CF895C Square Subsets (组合数+状压DP+简单数论)

题目大意:给你一个序列,你可以在序列中任选一个子序列,求子序列每一项的积是一个平方数的方案数。

1<=a[i]<=70

因为任何一个大于2的数都可以表示成几个质数的幂的乘积

所以我们预处理70以内的质数,把它作为二进制状压的状态,每个在序列中出现数Hash一下,组合数推一下

所以把奇次幂的状态表示为1,偶次幂的状态就是0,比如6就是11,42就是1011

而平方数的每个质因子的指数都是偶数,所以最终结果的状态就是0000000...

转移的过程,两个数的乘积,就是这两个数的质因子二进制的状态的合并,即异或(xor)运算

卡常很恶心,懒得进一步优化了

好吧据说可以推出结论,这个组合数加起来其实是2的幂次

#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 100100
#define M 75
#define mod 1000000007
#define C(m,n) (((fac[n]*inv[m])%mod*inv[n-m])%mod)
#define ll long long 
using namespace std;

int xx,n,now,lst;
int hx[M];
ll x,y,t;
ll inv[N],fac[N],f[2][(1<<19)+100];
int pr[19]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67};
int gc()
{
	int rett=0,fh=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-')fh=-1; c=getchar();}
	while(c>='0'&&c<='9') {rett=rett*10+c-'0';c=getchar();}
	return rett*fh;
}
void exgcd(ll a,ll b)
{
    if(b==0) {x=1,y=0;}
    else {exgcd(b,a%b);t=x;x=y;y=t-a/b*y;}
}
void get_inv()
{
    inv[0]=inv[1]=1,fac[0]=fac[1]=1;
    for(ll i=2;i<=n;i++) 
    {
        exgcd(i,mod);
        fac[i]=(fac[i-1]*i)%mod;
        x=(x%mod+mod)%mod;
        inv[i]=(inv[i-1]*x)%mod;
    }
}

int main()
{
    //freopen("aa.in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) xx=gc(),hx[xx]++;
    get_inv();
    now=1,lst=0,f[0][0]=1;
    for(int i=1;i<=70;i++)
    {
        if(!hx[i]) continue;
        int s=0,x=i;
        for(int j=0;j<19;j++)
        {
            while(x%pr[j]==0) 
            {
                s^=(1<<j);
                x/=pr[j];
            }
        }
        for(int p=0;p<(1<<19);p++)
        {
      		if(!f[lst][p]) continue;
            for(int j=0;j<=hx[i];j++)
            {
                if(j&1)
                {
                    f[now][p^s]+=(f[lst][p]*C(j,hx[i]))%mod;
                    f[now][p^s]%=mod;
                }else{
                    f[now][p]+=(f[lst][p]*C(j,hx[i]))%mod;
                    f[now][p]%=mod;
                }  
            }
            f[lst][p]=0;
        }
        swap(now,lst);
    }
    printf("%I64d\n",f[lst][0]-1);
    return 0;
}

    
    

猜你喜欢

转载自blog.csdn.net/guapisolo/article/details/81750534