C. Square Subsets 题解(状压dp)

题目链接

题目思路

首先呢,状压\(dp\)还是可以想到的

发现只有20个质数,设\(dp[i][j]\)表示前\(i\)个数状态为\(j\)的方案数

但是时间复杂度明显不允许这样复杂为\(n*2^{20}\)

考虑优化

发现数字非常少只有70个,而且对于最后状态的影响,大小为\(i\)的数的个数只和奇偶有关

那么我们设\(dp[i][j]\)表示选取了大小\(1-i\)的数中状态为\(j\)的方案数

注意\(c(n,0)+c(n,2)+c(n,4)...=c(n,1)+c(n,3)+c(n,5)...=2^{n-1}\)

代码

#include<bits/stdc++.h>
#define fi first
#define se second
#define debug cout<<"I AM HERE"<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define  pii pair<long long,long long >
//typedef pair<long long,long long > pii
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1e9+7;
const double eps=1e-6;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n;
int a[maxn];
int isprime[80];
int tot;
int msk[80];
ll pw[maxn];
int cnt[80];
int dp[71][1<<19];
bool check(int x){
    for(int i=2;i*i<=x;i++){
        if(x%i==0) return 0;
    }
    return 1;
}
void init(){
    for(int i=2;i<=70;i++){
        if(check(i)){
            isprime[i]=tot++;
        }
    }
    for(int i=1;i<=70;i++){
        int now=i;
        for(int j=2;j*j<=now;j++){
            if(now%j==0){
                int tot=0;
                while(now%j==0){
                    tot++;
                    now/=j;
                }
                if(tot%2){
                    msk[i]|=(1<<isprime[j]);
                }
            }
        }
        if(now!=1){
            msk[i]|=(1<<isprime[now]);
        }
    }
}
signed main(){
    init();
    scanf("%d",&n);
    pw[0]=1;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        pw[i]=pw[i-1]*2%mod;
        cnt[a[i]]++;
    }
    dp[0][0]=1;
    for(int i=1;i<=70;i++){
        for(int j=0;j<(1<<tot);j++){
            if(cnt[i]==0){
                dp[i][j]+=dp[i-1][j];
            }else{
                dp[i][j]+=dp[i-1][j]*pw[cnt[i]-1]%mod;
                dp[i][j]+=dp[i-1][j^msk[i]]*pw[cnt[i]-1]%mod;
            }
            dp[i][j]%=mod;
        }
    }
    printf("%d\n",(dp[70][0]-1+mod)%mod);
    return 0;
}

原文地址:https://www.cnblogs.com/hunxuewangzi/p/15533450.html