题解 CF895C Square Subsets
这伞兵题目花了我两个小时?!
求积是平方数的给定数组子序列个数。
注意到数据范围 \(a_i \le 70\),那么就只有 \(19\) 个质数,可以记录每一个质数。
又因为积只关心所有质因数的个数的奇偶性,所以把它们对 \(2\) 取模,那么就可以愉快地状压了。
但还是有点不对, \(n\) 有 \(10^5\) 呢!然后我就没有办法了。。。
其实对每个数做 dp 也是没有意义的,因为 \({n \choose 0} + {n \choose 2} + {n \choose 4} + \cdots\) 的结果就是 \(2^{n-1}\), 怎么证明这一点呢?可以用归纳法来证明。
首先这个过程就相当于二进制数的 popcount
的奇偶性。
- 基本情况:\(n=1\) 的时候显然成立。
- 当 \(n = k\) 成立的时候,我们再在前面给它加一个 \(0\) 和 \(1\),保证原来奇偶性的个数都翻了一倍,所以答案就乘上了 \(2\),结论同样成立
那么就真的可以 dp 了!不过要注意预处理二的幂次,因为 \(2^{19} \times 70\) 还是比较大的,里面再来个 \(\log\) 就 T 了。
代码
#include <iostream>
#define int long long
const int P = 1000000007;
int prime[19] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67};
int tr[100], cnt[75][20], sta[75], tot[75], f[1 << 19], g[1 << 19], n, pw[100005];
int Pow(int a, int b) {
return pw[b];
int an = 1;
for ( ; b; b >>= 1, a = a * a % P)
if (b & 1) an = an * a % P;
return an;
}
void add(int &a, int b) { a = (a+b) % P; }
signed main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
std::cin >> n;
for (int i = 0; i < 19; i++) tr[prime[i]] = i+1;
for (int i = 1, x; i <= n; i++)
std::cin >> x, tot[x] ++;
for (int i = 2; i <= 70; i++) {
int x = i;
for (int j = 2; j*j <= x; j++)
if (x % j == 0)
while (x % j == 0) cnt[i][tr[j]] ++, x /= j;
if (x != 1) cnt[i][tr[x]] ++;
}
for (int i = 1; i <= 70; i++)
for (int j = 1; j <= 19; j++)
if (cnt[i][j] & 1) sta[i] |= 1 << (j-1);
pw[0] = 1;
for (int i = 1; i <= n; i++) pw[i] = pw[i-1] * 2 % P;
f[0] = 1;
for (int i = 2; i <= 70; i++) {
if (!tot[i]) continue;
for (int S = 0; S < (1 << 19); S++) g[S] = 0;
for (int S = 0; S < (1 << 19); S++)
add(g[S ^ sta[i]], Pow(2, tot[i]-1) * f[S] % P),
add(g[S], f[S] * Pow(2, tot[i]-1) % P);
for (int S = 0; S < (1 << 19); S++) f[S] = g[S];
}
std::cout << (f[0] * Pow(2, tot[1]) % P - 1 + P) % P;
}
那么我做那么久问题出在哪里呢?
首先是没有想到那个组合数居然可以直接求,也没有想清楚 dp 能不能做就直接写了,写到一半才发现。
其次再去写的时候也没有想清楚,在滚动的时候一些细节上的处理出了问题,写得慢也调得慢。
希望下次能按照 SOP 搞清楚了一切再快速写出代码来。