P8292 [省选联考 2022] 卡牌
*VII. P8292 [省选联考 2022] 卡牌
套路见少了,省选考场上被打爆咧。
首先我们尝试抽象题意:给出
虽然不知道计数能否归约到最优化上,但对于上述问题的变种 “求最少选出多少个集合覆盖
自然,思考本题的
根号分治的核心要素在于,不存在一个数,使得它含有两个以上大于
对于小于
首先,将只含有
求
对于剩下来的
注意,这里实际上我们扩展了
考虑一个
更具体地,我们枚举所有
总结一下,我们首先求出
注意对给出的
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3 + 2;
const int V = 1 << 13;
const int mod = 998244353;
const int P[13] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41};
void add(int &x, int y) {x += y, x >= mod && (x -= mod);}
void sub(int &x, int y) {x -= y, x < 0 && (x += mod);}
int n, m, pw[N * N], buc[N], f[V], g[N][V];
void init() {
for(int i = pw[0] = 1; i <= n; i++) pw[i] = (pw[i - 1] << 1) % mod;
for(int i = 1; i < N; i++) {
int tmp = i, msk = 0;
if(!buc[i]) continue; // 1 = 2 ^ 0,所以加 0 就直接忽略了
for(int j = 0; j < 13; j++) while(tmp % P[j] == 0) tmp /= P[j], msk |= 1 << j; // 求出每个 i 覆盖 P 中质数的情况
for(int j = 0; j < V; j++) if((j & msk) == msk) f[j] += buc[i]; // 对于包含 msk 的位置,系数要乘以 2 ^ {c_i} 次方,因此 2 的幂次加上 buc[i]
if(tmp == 43 * 43) tmp = 43; // 注意这里有个小特判,43 ^ 2 <= 2e3
if(tmp > 1) for(int j = 0; j < V; j++) if((j & msk) == msk) g[tmp][j] += buc[i]; // 这里是求每个 G_p
}
}
int main() {
freopen("card.in", "r", stdin);
freopen("card.out", "w", stdout);
cin >> n;
for(int i = 1, s; i <= n; i++) scanf("%d", &s), buc[s]++;
cin >> m, init();
while(m--) {
int c, p[V << 1], h[V], msk = 0, ans = 0;
cin >> c, memcpy(h, f, sizeof(h));
for(int i = 1; i <= c; i++) cin >> p[i];
sort(p + 1, p + c + 1), c = unique(p + 1, p + c + 1) - p - 1; // 对所有质数去重
for(int i = 1; i <= c; i++) for(int j = 0; j < 13; j++) if(p[i] == P[j]) msk |= 1 << j; // 求出 T \cap P
for(int i = 1; i <= c; i++) if(p[i] > 41) for(int j = 0; j < V; j++) h[j] -= g[p[i]][j]; // 对于 >= 43 且出现在 T 中的质数,先减去它们的贡献
for(int j = 0; j < V; j++) h[j] = pw[h[j]]; // 注意乘以 G_p - x ^ 0 时系数不再是 2 的幂,所以提前将系数从幂次转化为真实值
for(int i = 1; i <= c; i++) if(p[i] > 41) for(int j = 0; j < V; j++) h[j] = 1ll * h[j] * (pw[g[p[i]][j]] - 1) % mod; // 乘以 G_p - x ^ 0,注意 -x ^ 0 在点值意义下即每个位置上均为 -1,所以只需乘以每个位置原来的系数 -1 即可
for(int k = 1; k < V; k <<= 1) for(int i = 0; i < V; i += k << 1) for(int j = 0; j < k; j++) sub(h[i | j | k], h[i | j]); // 将点值转化为真实值,高维差分 IFWT
for(int j = 0; j < V; j++) if((msk & j) == msk) add(ans, h[j]); // 对能完全覆盖 T \cap P 的方案数求和
cout << ans << endl;
}
return 0;
}
/*
2022/4/18
start coding : 10:52
finish debugging : 11:47
--- stupid mistakes ---
None. Good.
*/