C. Square Subsets
题目链接
给一个序列ai,长度n,问有多少种方法可以选一些数字出来,且使得这些数字乘积是一个平方数。
n<=1e5 , ai<=70
任意一个平方数可以表示为p1^a1 * p2^a2 * ……其中p1表示质数,a1是一个偶数,且我们不关心具体是多少只需要知道是奇偶即可,那么可以直接用01来表示。
观察到ai比较小,我们直接把70以内的数字拆分成质因子表示形式,然后用二进制来表示它的幂,然后状压计数即可。
#include<bits/stdc++.h>
using namespace std;
#define LONG long long
#define clr0(x) memset(x , 0 , sizeof x)
const LONG MOD = 1e9 + 7 ;
int take[77] ;
int pw[77] ;
int ss[77] ;
LONG POW[100100] ;
int f[73][(1<<19 ) +100] ;
LONG Qpow(LONG a , int b )
{
return POW[b] ;
}
int main()
{
POW[0] = 1 ;
for(int i = 1 ;i <= 100000 ; ++ i)
POW[i] = POW[i-1] * 2 %MOD ;
int T = -1 ;
for(int i = 2 ; i <= 70 ; ++ i)
{
int judge = 0 ;
for(int j = 2;j < i ; ++ j)
if(i %j ==0 ) judge = 1;
if(!judge )
{
T ++ ;pw[i] = (1<< T ) ;
}
}
for(int i = 2 ; i <= 70 ; ++ i)
{
int tmp= i ;ss[i] = 0 ;
for(int j = 2;tmp > 1;)
{
if(tmp % j == 0)
{
tmp /= j ;
ss[i] ^= pw[j] ;
j = 2;
}
else j ++ ;
}
}
clr0(take) ;
int n , a ;
scanf("%d",&n) ;
int tot =0 ;
for(int i = 1; i<= n ; ++ i)
scanf("%d",&a) , take[a] ++ ;
clr0(f) ;
f[0][0] = 1;int p = 0;
for(int i = 2 ; i <= 70 ; ++ i)
{
if(take[i] <= 0 ) continue ;
p ++ ;
for(int j = 0 ; j < (1 <<19 ) ; ++ j )
{
f[p][j] += (int ) ( Qpow( 2, take[i] - 1 ) * f[p-1][j] % MOD );
f[p][j^ss[i]] += (int )( Qpow( 2 , take[i] -1 ) * f[p-1][j ] % MOD ) ;
}
}
printf("%lld\n" , Qpow(2 , take[1] ) * f[p][0] % MOD - 1 ) ;
}