Square Subsets
题意:从n个数中选出一个非空子集,使得集合中的数的乘积是完全平方数;
思路:首先什么是完全平方数?就是能表示为一个数的平方的数;同时如果x是完全平方数,那么他拥有的质因子p的个数一定为偶数;
n个数的范围是[1, 70],1~70一共19个质数;所以我们可以把19个质数的个数的奇偶性压缩成01状态,0表示有偶数个,1表示有奇数个,那么对于选出来的数的质因数的状态如果19个二进制位全为0,则表示乘积一定构成了完全平方数;
用num[i]记录数i出现的次数,sta[i]表示i中的质因子的状态,如:sta[10=3*5]=0000000000000000101,(由右向左第i位表示第i个质数), sta[45=3*3*5]=0000000000000000100;
dp[i][j]表示从1选到i,乘积是j状态的方案数;那么我们最后的答案是dp[70][0]-1,状态是0才说明乘积是完全平方数,减1是要去掉空集;
还有一个组合数学公式C(m, n)表示n个物品选m个方案数, ,
;
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int cnt_p, vis[71], n, sta[71];
const int mod=1e9+7;
int num[71], pow_2[100010], prime[20], dp[71][(1<<19)+5];
void init(){
//筛出70内的素数;
cnt_p=0;
for(int i=2; i<=70; i++){
if(!vis[i]){
prime[cnt_p++]=i;
for(int j=i+i; j<=70; j+=i){
vis[j]=1;
}
}
}
//计算2^i; pow_2[i]表示2的i次方:2^i;
pow_2[0]=1;
for(int i=1; i<=100000; i++){
pow_2[i]=pow_2[i-1]*2%mod;
}
//输入n个数;
scanf("%d", &n);
for(int i=0; i<n; i++){
int x;
scanf("%d", &x);
num[x]++;//计算x出现的次数;
}
//计算i中的第j个质因子的个数的奇偶性, 0表示偶数个, 1表示奇数个;
for(int i=1; i<=70; i++){
int k=i;
for(int j=0; j<cnt_p&&prime[j]<=k; j++){
while(k%prime[j]==0){
k/=prime[j];
sta[i]^=(1<<j);
}
}
}
}
int main(){
init();
dp[0][0]=1;
int tot=(1<<19)-1;
for(int i=1; i<=70; i++){
if(num[i]==0){
//i未出现;
for(int j=0; j<tot; j++){
dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
}
}
else{
//i出现;
for(int j=0; j<=tot; j++){
//如果i选了奇数次,得到的状态的是j^sta[i];此时增加的方案数为:(C(1, num[i])+C(3, num[i])+...)*dp[i-1][j]=2^(num[i]-1)*dp[i-1][j];
dp[i][j^sta[i]]=(dp[i][j^sta[i]]+(ll)pow_2[num[i]-1]*dp[i-1][j]%mod)%mod;
//如果i选了偶数次,得到的状态是j;此时增加的方案数为:(C(0, num[i])+C(2, num[i])+...)*dp[i-1][j]=2^(num[i]-1)*dp[i-1][j];
dp[i][j]=(dp[i][j]+(ll)pow_2[num[i]-1]*dp[i-1][j]%mod)%mod;
}
}
}
//减1是要去掉空集的情况; dp[i][j]表示前i个数的乘积是j状态的方案数,j=0就表示质因子的个数为偶数个,则必定是某个数的平方;
printf("%d\n", (dp[70][0]-1+mod)%mod);
return 0;
}