Codeforces Round448 C Square Subsets

Codeforces Round448 C

题目大意

n个数(n<=1e5),选其中一部分数相乘,结果为一个完全平方数,问有多少种选法,每个数的范围为[1,70]。

思路

考虑到,完全平方数是那些每种质因子个数都为偶数的数,所以把每个数都分解成质因子相乘的形式,同时对每种质因子的个数对%2,因为70以内的质数只有19个,所以可以把每个数压位,第i位表示这个数的因子是否有奇数个第i个质数。

解法一:压位DP

又因为数的范围只从1~70,所以我们记录每个数出现的次数,有一个dp方程,dp[i][j] 表示考虑前i个数,相乘结果为j(压位后)的方法数(包括一个不选)。

对于当前的(i,j),是由前i-1个数中得到j的值不异或当前tmp的方法数,得到的j异或当前tmp的方法数,选奇数个为异或tmp,选偶数个为不 异或tmp,所以共有2

的a[i]-1次方种。

于是有\(dp[i][j]=2^{a[i]-1}*(dp[i-1][j]+dp[i-1][j\bigoplus tmp])\) ,其中a[i] 为第i个数的个数,tmp表示对这个数压位后的形式。

滚动数组写法:

for(int j=0;j<(1<<19);j++){
  dp2[j]=mi[a[i]-1]*(dp1[j]+dp1[j^tmp])%mod;
}
swap(dp1,dp2);

最后减去一个都不选的情况。

完整代码:

//
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<queue>
#include<vector>
#include<map>
#define N 100005
const int inf=0x3f3f3f3f;
#define rep(i,n) for(i=0;i<n;i++)
#define mem(a,x) memset(a,x,sizeof(a))
const double PI=acos(-1);
const double eps=1e-9;

using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int ans,t,i,j,k,n,m;
int a[N],b[N];
ll mi[N];
int pr[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67};
ll dp1[1<<20],dp2[1<<20]; 
char s[N]; 
void init(){
    mi[0]=1;
    for(int i=1;i<N;i++){
        mi[i]=mi[i-1]*2%mod;
    }
}
int bitmask(int x){
    int tmp=0;
    for(int i=0;i<19;i++){
        while(x%pr[i]==0){
            x/=pr[i];
            tmp^=(1<<i);
        }
    }
    return tmp;
}
int main(){
    init();
    while(scanf("%d",&n)!=EOF)
    {
        //mem(dp2,0);
        //mem(dp1,0);
        //mem(a,0);
        for(int i=0;i<n;i++){
            int x;
            scanf("%d",&x);
            a[x]++;
        }
        dp1[0]=1;
        for(int i=1;i<=70;i++){
            if(a[i]){
                int tmp=bitmask(i);
                for(int j=0;j<(1<<19);j++){
                    dp2[j]=mi[a[i]-1]*(dp1[j]+dp1[j^tmp])%mod;
                }
                swap(dp1,dp2);
            }
        }
        printf("%lld\n",(dp1[0]-1+mod)%mod); 
    }
    return 0;
}

/*

4
1 1 1 1
4
2 2 2 2
5
1 2 4 5 8

*/

解法二:线性基

想到压位就想到线性基,简单回顾下线性基:

T的线性基是T的一个子集A={a1,a2,a3,...,an}。
A中元素互相xor所形成的异或集合,等价于原数集T的元素互相xor形成的异或集合。
可以理解为将原数集进行了压缩。

做一个每位存储的是质因子的个数%2的线性基。

对n个数遍历存入线性基中,最终无法被存入线性基中的数可以视作与线性基中的某几个数做异或操作后变成一个完全平方数。设有t个数无法被存入线性基中,

那么答案就为\(2^t-1\)


#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
#define N 100005
int pr[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67};
vector<int>base;
ll mi[N];
int T,n,m;
ll t;
const ll mod=1e9+7;
int main(){
        mi[0]=1;
        for(int i=1;i<N;i++){
            mi[i]=mi[i-1]*2%mod;
        }
        
        scanf("%d",&n);
        
        int flag=0;
        for(int i=0;i<n;i++)
        {
            int x;
            scanf("%d",&x);
            int cnt=0;
            for(int j=0;j<19;j++){
                int tmp=0;
                while(x%pr[j]==0){
                    x/=pr[j];
                    cnt^=(1<<j);
                }
            }
            
            for(int j=0;j<base.size();j++){
            //  printf("%d %d\n",base[j],cnt);
                cnt=min(cnt,base[j]^cnt);
            }
            if(cnt){
                base.push_back(cnt);
            }
            else{
                t++;
            }
        }
        for(int i=0;i<base.size();i++) printf("%d%c",base[i],i==base.size()-1?'\n':' ');
        //printf("%d %d\n",t,base.size()); 
        printf("%lld\n",(mi[t]-1+mod)%mod);
    
    return 0;
} 

转载于:https://www.cnblogs.com/Crazycatmiao/p/7911711.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值