题目大意:给你一个数组,问有多少个非空子集满足集合中所有数字的乘积是一个平方数。结果mod 1e9+7。
首先就可以想到把所有数出现的次数记录一下。
由于唯一分解定理可知若一个数是平方数,那么它唯一分解后质因子的指数一定是偶数。
而且数字都是小于70的,小于70的质数只有20个,于是就可以进行状压dp了。
第 i 位为1表示唯一分解后第 i 个质数的质数为奇数,否则为偶数。
接着考虑状态转移。
若数 k 没出现,即 cnt[ k ] =0,那么 dp[ k ][ j ]=dp[ k-1 ][ j ],
若 k 出现,且出现次数为 n ,从 n 个中选出现 m 次,显然若 m 为偶数不影响状态,m为奇数则影响。
因为 C (0,n) + C ( 1 ,n) +....+C (n,n)= 2^n, 所以 C (0,n) + C(2,n)+...=C ( 1,n ) +C(3 , n )+... = 2 ^ (n-1) 。
那么状态转移方程为
dp [i][j] = dp[i][j]+ dp[i-1][j] * 2 ^( n-1);
dp [i][ j ^ status[k] ] = dp[i][ j^status[k] + dp[i-1][j] * 2^(n-1);
由于内存问题所以要注意使用滚动数组。
最后要减去全部选的 1 不要忘记。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const ll mod=1e9+7;
ll p[100005];
ll status[71];
ll dp[2][(1<<19)];
ll cnt[71];
ll prime[71];
bool vis[71];
void init()
{
p[0]=1;
memset(status,0,sizeof(status));
memset(vis,false,sizeof(vis));
for(int i=1;i<maxn;i++)
p[i]=(p[i-1]*(ll)2)%mod;
vis[0]=vis[1]=true;
for(int i=2;i<71;i++)
{
if(!vis[i])
{
for(int j=2;i*j<71;j++)
vis[i*j]=true;
}
}
int cnt=0;
for(int i=2;i<71;i++)
{
if(!vis[i])
prime[cnt++]=i;
}
for(int i=1;i<71;i++)
{
int t=i;
for(int j=0;j<cnt;j++)
{
int num=0;
while(t%prime[j]==0)
{
num++;
t/=prime[j];
}
if(num%2)
status[i]+=(1<<j);
}
}
}
int main()
{
int n,x;
init();
while(cin>>n)
{
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++)
{
cin>>x;
cnt[x]++;
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=70;i++)
{
int now=i&1,last=!now;
for(int j=0;j<(1<<19);j++)
{
if(cnt[i])
{
dp[now][j]=(dp[now][j]+dp[last][j]*p[cnt[i]-1]%mod)%mod;
dp[now][j^status[i]]=(dp[now][j^status[i]]+dp[last][j]*p[cnt[i]-1]%mod)%mod;
}
else dp[now][j]=dp[last][j];
}
for(int j=0;j<(1<<19);j++)
dp[last][j]=0;
}
cout<<(dp[0][0]-1+mod)%mod<<endl;
}
return 0;
}