CF895C dp/线性基

版权声明:本文为博主原创文章,转载标明出处。 https://blog.csdn.net/xlzhang223/article/details/78736846

链接:点击打开链接

题意:n个数,选子集乘积之和是完全平方数的子集个数

思路:首先题目中ai非常小,显然可以状态压缩因子把问题转化成异或和为0的方案数字。

在此基础上可以DP解决或者使用线性基

DP方法

用dp[i][j]表示i次插入时异或和为j的方案数,我们可以想到插入一个新数字就是   dp[i-1][j ^ st]+=dp[i-1][j]

但是到此每插入一个数都要遍历j (2^19) ,所以我们考虑用桶压缩输入,预处理出来n个数中选奇数个或者选偶数个元素的方案数。组合计数一下便可以解决问题。

具体看代码。

DP

#include <bits/stdc++.h>
#define PB push_back
#define MP make_pair
#define X first
#define Y second
#define pii pair<int,int>
#define MEM(x) memset(x,0,sizeof(x))
using namespace std;
int n,m,x;
const int p=1e9+7,maxn=1e6+10;
int idx[100];
vector<int> Pr;
bool isprime(int x){
    for(int i=2;i<x;i++)
        if(x%i==0) return false;
    return true;
}
int getst(int x){
    if(x==1) return 0;
    int st=0;
    for(int i=0;i<Pr.size();i++)
        while(x%Pr[i]==0) st^=(1<<i),x/=Pr[i];
    return st;
}
long long C1[maxn],C2[maxn];
long long dp[maxn],last[maxn];
int main(){
    MEM(C1);MEM(C2);MEM(idx);MEM(dp);
    for(int i=2;i<=70;i++)if(isprime(i)) Pr.PB(i);
    C1[1]=1;
    for(int i=2;i<maxn;i++){
        C1[i]=(C1[i-1]+C2[i-1]+1)%p;
        C2[i]=(C2[i-1]+C1[i-1])%p;
    }
    scanf("%d",&n);
    int cnt=0;
    dp[0]=last[0]=1;
    for(int i=0;i<n;i++) scanf("%d",&x),idx[x]++;
    for(int i=1;i<=70;i++){
        if(idx[i]==0) continue;
        int st=getst(i);
        for(int j=0;j<maxn;j++){
            dp[j^st]=(dp[j^st]+last[j]*C1[idx[i]])%p;
            dp[j]=(dp[j]+last[j]*C2[idx[i]])%p;
        }
        for(int j=0;j<maxn;j++) last[j]=dp[j];
    }
    printf("%lld\n",(dp[0]-1+p)%p);
    return 0;
}


线性基方法: 

 首先线性基的概念参考点击打开链接(2017西安赛区考了这个),根据性质分析这个问题

X插入失败     ----> 存在可以构成X的子集 

已知和为0的集合 ---->  若插入失败
                        设有子集{S1},{S2},{S3}没有交集 
                      则一定存在{S1}^{S2}=0 {S2}^{S3}=x 
                        则可以替换S2为{S3}^x 答案*=2
                        (其中S2、S3可以是空集) 
综上所述,线性基插入是失败时,和为0的子集数量*=2

代码:

#include <bits/stdc++.h>
#define PB push_back
#define MP make_pair
#define X first
#define Y second
#define pii pair<int,int>
#define MEM(x) memset(x,0,sizeof(x))
using namespace std;
int n,m,x;
const int p=1e9+7;
struct L_B{
    long long d[61];
    L_B(){memset(d,0,sizeof(d));}
    bool insert(long long val){
        for (int i=60;i>=0;i--)
            if (val&(1LL<<i)){
                if (!d[i]){
                    d[i]=val;
                    break;
                }
                val^=d[i];
            }
        return val>0;
    }
};
vector<int> Pr;
bool isprime(int x){
    for(int i=2;i<x;i++)
        if(x%i==0) return false;
    return true;
}
int getst(int x){
    int st=0;
    for(int i=0;i<Pr.size();i++)
        while(x%Pr[i]==0) st^=(1<<i),x/=Pr[i];
    return st;
}
int main(){
    for(int i=2;i<=70;i++)if(isprime(i)) Pr.PB(i);
    scanf("%d",&n);
    L_B B;
    int cnt=0;
    long long ans=1;
    for(int i=0;i<n;i++){
        scanf("%d",&x);
        if(!B.insert(getst(x))) ans=(ans*2)%p;
    }
    printf("%lld\n",(ans-1+p)%p);
    return 0;
}



猜你喜欢

转载自blog.csdn.net/xlzhang223/article/details/78736846