题目链接
【CodeForces 895E】Square Subsets
题目大意
给定一个大小为
n
n
的集合,求
s
s
有多少个非空子集中的数的乘积为完全平方数。
,
si≤70
s
i
≤
70
题解
首先我们发现,集合中的数都很小。
并且,因为求的是完全平方数的个数,所以我们只关心每个素因数出现的奇偶性。
于是我们便可以把每一个数压缩成一个状态,包含每个素因数出现次数的奇偶性。
具体方法是:当我们得到一个数时,将其分解素因数,若第
i
i
个素数出现次数为奇数,则状态的第位为
1
1
,否则状态的第位为
0
0
。
将数的状态记为
status[n]
s
t
a
t
u
s
[
n
]
。
很容易发现:
0≤status[n]<2primecount(maxsi)
0
≤
s
t
a
t
u
s
[
n
]
<
2
p
r
i
m
e
c
o
u
n
t
(
max
s
i
)
。
最初的思路就是对于每一个数和每一个压缩后的状态进行动态规划,但是状态数量太大了。
如何优化?无法优化,只能换一种思路。
发现答案只和每个数出现的次数有关。
dp[i][mask]
d
p
[
i
]
[
m
a
s
k
]
表示只选
i
i
以内的数除去完全平方数剩余数的压缩状态为的时候答案为几。
设
f[i][0/1]
f
[
i
]
[
0
/
1
]
表示选偶数个/奇数个
i
i
的方案数量。
状态转移方程:。
具体细节见代码。
代码
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
const int maxn = 100005;
const int maxm = 70;
const int mod = 1000000007;
typedef pair<int,int> pii;
int n, a, b[maxn];
int m, p[maxm], v[maxm];
int f0[maxm], f1[maxm], dp[maxm+5][1<<19];
bool isp[maxm];
int main() {
memset(isp, 1, sizeof(isp));
for (int i = 2; i <= maxm; i++) {
if(isp[i]) {
p[m] = i;
v[m] = 1 << m;
m++;
for (int j = i << 1; j <= maxm; j += i) {
isp[j]=0;
}
}
}
for (int i = 1; i <= maxm; i++) {
f0[i] = 1;
int _=i;
for (int j = 0; j < m; j++) {
while (_ % p[j] == 0) {
_ /= p[j];
b[i] ^= v[j];
}
}
}
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a);
f0[a] = f1[a] = (f0[a] + f1[a]) % mod;
}
dp[0][0] = 1;
for (int i = 1; i <= maxm; i++)
for (int j = 0; j < 1<<19; j++)
dp[i][j] = (1ll * dp[i-1][j] * f0[i] + 1ll * dp[i-1][j^b[i]] * f1[i]) % mod;
printf("%d\n", dp[maxm][0] - 1);
return 0;
}
总结
完全平方数状态压缩的技巧十分重要,在许多题目中都会出现。