一类对质因子进行转态压缩的 trick
stripe_python · · 算法·理论
由于本人在 9.26 模拟赛中的 T2 二十分钟瞪出了做法,然后自己否掉导致写了 2h 喜提 240pts,故对这类题目作一个总结。
顺带一提,这类题对质因子状压以后还可以考虑哈集幂(
AT_abc195_f [ABC195F] Coprime Present
这题就是本人模拟赛的那个 T2。
::::success[题解]
一个思路是注意到最大答案在 __int128 状压和神秘减枝,赛时想了 1.5h 无果。
回归题目发现
若
通过打表发现,
记
边界为
复杂度
::::info[代码] 赛时 AC 代码。
#define int long long
const int N = 21;
int l, r, f[1 << N];
const int p[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71}; // 20个
void _main() {
cin >> l >> r;
f[0] = 1;
for (int i = l; i <= r; i++) {
int x = 0;
for (int j = 0; j < 20; j++) {
if (i % p[j]) continue;
x |= 1 << j;
}
for (int j = 0; j < (1 << 20); j++) {
if ((x & j) == 0) f[x | j] += f[j];
}
}
int res = 0;
for (int i = 0; i < (1 << 20); i++) res += f[i];
cout << res;
}
::::
P2150 [NOI2015] 寿司晚宴
::::success[题解] 两个子集中的数字两两互质,等价于子集中的质因数集合交集为空。
我们先从 30pts 开始考虑。因为
然后对于
然后直接考虑正解。因为
具体地,对于
把
滚动数组优化空间,注意要像 01 背包那样倒序枚举了。答案即为
至此,时间复杂度优化到
::::info[代码]
const int N = 505, M = 1 << 8;
int prime[] = {0, 2, 3, 5, 7, 11, 13, 17, 19};
int n, p;
struct node {
int v, p, s;
node() : v(0), p(-1), s(0) {}
void get() {
int x = v;
for (int i = 1; i <= 8; i++) {
if (x % prime[i]) continue;
s |= 1 << (i - 1);
while (x % prime[i] == 0) x /= prime[i];
}
if (x != 1) p = x;
}
} a[N];
int dp[N][N], f[N][N], g[N][N];
void _main() {
cin >> n >> p;
for (int i = 2; i <= n; i++) a[i].v = i, a[i].get();
sort(a + 2, a + n + 1, [](const node& x, const node& y) -> bool {
return x.p < y.p;
});
dp[0][0] = 1;
for (int i = 2; i <= n; i++) {
if (i == 2 || a[i].p != a[i - 1].p || a[i].p == -1) { // 新连续段
memcpy(f, dp, sizeof(f)), memcpy(g, dp, sizeof(g));
}
#define add(x, y) ((x) += (y), (x) >= p ? (x) -= p : (x))
#define madd(x, y) ((x) + (y) >= p ? (x) + (y) - p : (x) + (y))
#define msub(x, y) ((x) - (y) < 0 ? (x) - (y) + p : (x) - (y))
for (int s = M - 1; s >= 0; s--) {
for (int t = M - 1; t >= 0; t--) {
if (s & t) continue;
if ((a[i].s & t) == 0) add(f[a[i].s | s][t], f[s][t]);
if ((a[i].s & s) == 0) add(g[s][a[i].s | t], g[s][t]);
}
}
if (i == n || a[i].p != a[i + 1].p || a[i].p == -1) { // 下一个点是新连续段
for (int s = 0; s < M; s++) {
for (int t = 0; t < M; t++) {
if (s & t) continue;
dp[s][t] = msub(madd(f[s][t], g[s][t]), dp[s][t]);
}
}
}
}
int res = 0;
for (int s = 0; s < M; s++) {
for (int t = 0; t < M; t++) {
if (s & t) continue;
add(res, dp[s][t]);
}
} cout << res;
}
::::
P8292 [省选联考 2022] 卡牌
::::success[题解]
注意到
由于
这样是一个容斥的过程,答案为
复杂度
考虑优化。注意到
称
考虑合并两部分的答案。对于小质数,使用上面的容斥式子即可。对于大质数,考虑对于集合
最终复杂度
::::
::::info[代码]
const int N = 2005, M = 1e6 + 5;
int n, q, u, v, c[N], p[N], id[N], f[1 << 14][N], sum[1 << 14];
mint pw[M];
pair<int, int> num[N];
bitset<N> isprime;
void prework() {
pw[0] = 1;
for (int i = 1; i < M; i++) pw[i] = pw[i - 1] * 2;
isprime.set(), isprime[0] = isprime[1] = 0;
for (int i = 2; i < N; i++) {
if (!isprime[i]) continue;
p[++p[0]] = i;
for (int j = i << 1; j < N; j += i) isprime[j] = 0;
}
for (int i = 1; i <= p[0]; i++) id[p[i]] = i;
}
mint ask(const vector<int>& a) {
int s = 0;
vector<int> vec;
for (int x : a) {
if (x <= 41) s |= 1 << (id[x] - 1);
else vec.emplace_back(id[x]);
}
mint res = 0;
for (int t = s; ; t = (t - 1) & s) {
mint cur = 1;
int cnt = sum[t];
for (int x : vec) cnt -= f[t][x], cur *= pw[f[t][x]] - 1;
if (popcount(t) & 1) res -= cur * pw[cnt];
else res += cur * pw[cnt];
if (t == 0) break;
} return res;
}
void _main() {
prework();
cin >> n;
for (int i = 1; i <= n; i++) cin >> u, c[u]++;
for (int i = 1; i < N; i++) {
int x = i, s = 0;
for (int j = 1; j <= 13; j++) {
if (x % p[j]) continue;
s |= 1 << (j - 1);
while (x % p[j] == 0) x /= p[j];
}
num[i] = {s, x};
}
num[43 * 43].second = 43;
for (int s = 0; s < (1 << 13); s++) {
for (int i = 1; i < N; i++) {
if (num[i].first & s) continue;
f[s][id[num[i].second]] += c[i], sum[s] += c[i];
}
}
for (cin >> q; q--; ) {
cin >> u;
vector<int> a(u);
for (int i = 0; i < u; i++) cin >> a[i];
cout << ask(a) << '\n';
}
}
::::