Square Subsets CodeForces - 895C (状压DP)

Square Subsets

 题目链接:CodeForces - 895C 

题意:从n个数中选出一个非空子集,使得集合中的数的乘积是完全平方数;

思路:首先什么是完全平方数?就是能表示为一个数的平方的数;同时如果x是完全平方数,那么他拥有的质因子p的个数一定为偶数;

n个数的范围是[1, 70],1~70一共19个质数;所以我们可以把19个质数的个数的奇偶性压缩成01状态,0表示有偶数个,1表示有奇数个,那么对于选出来的数的质因数的状态如果19个二进制位全为0,则表示乘积一定构成了完全平方数;

用num[i]记录数i出现的次数,sta[i]表示i中的质因子的状态,如:sta[10=3*5]=0000000000000000101,(由右向左第i位表示第i个质数), sta[45=3*3*5]=0000000000000000100;

dp[i][j]表示从1选到i,乘积是j状态的方案数;那么我们最后的答案是dp[70][0]-1,状态是0才说明乘积是完全平方数,减1是要去掉空集;

还有一个组合数学公式C(m, n)表示n个物品选m个方案数,\sum_{i=1}^{ }C(2*i-1, n)=2^{n-1}   ,  \sum_{i=0}^{ }C(2*i, n)=2^{n-1}

\sum_{i=0}^{n}C(i, n)=2^{n}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int cnt_p, vis[71], n, sta[71];
const int mod=1e9+7;
int num[71], pow_2[100010], prime[20], dp[71][(1<<19)+5];
void init(){
	//筛出70内的素数;
	cnt_p=0;
	for(int i=2; i<=70; i++){
		if(!vis[i]){
			prime[cnt_p++]=i;
			for(int j=i+i; j<=70; j+=i){
				vis[j]=1;
			}
		}
	}
	//计算2^i; pow_2[i]表示2的i次方:2^i; 
	pow_2[0]=1;
	for(int i=1; i<=100000; i++){
		pow_2[i]=pow_2[i-1]*2%mod;
	}
	//输入n个数; 
	scanf("%d", &n);
	for(int i=0; i<n; i++){
		int x;
		scanf("%d", &x);
		num[x]++;//计算x出现的次数; 
	}
	//计算i中的第j个质因子的个数的奇偶性, 0表示偶数个, 1表示奇数个; 
	for(int i=1; i<=70; i++){
		int k=i;
		for(int j=0; j<cnt_p&&prime[j]<=k; j++){
			while(k%prime[j]==0){
				k/=prime[j];
				sta[i]^=(1<<j);
			}
		}
	}
}
int main(){
	init();
	dp[0][0]=1;
	int tot=(1<<19)-1;
	for(int i=1; i<=70; i++){
		if(num[i]==0){
			//i未出现; 
			for(int j=0; j<tot; j++){
				dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
			}
		}
		else{
			//i出现; 
			for(int j=0; j<=tot; j++){
				//如果i选了奇数次,得到的状态的是j^sta[i];此时增加的方案数为:(C(1, num[i])+C(3, num[i])+...)*dp[i-1][j]=2^(num[i]-1)*dp[i-1][j]; 
				dp[i][j^sta[i]]=(dp[i][j^sta[i]]+(ll)pow_2[num[i]-1]*dp[i-1][j]%mod)%mod;
				//如果i选了偶数次,得到的状态是j;此时增加的方案数为:(C(0, num[i])+C(2, num[i])+...)*dp[i-1][j]=2^(num[i]-1)*dp[i-1][j];  
				dp[i][j]=(dp[i][j]+(ll)pow_2[num[i]-1]*dp[i-1][j]%mod)%mod;
			}
		}
	}
	//减1是要去掉空集的情况; dp[i][j]表示前i个数的乘积是j状态的方案数,j=0就表示质因子的个数为偶数个,则必定是某个数的平方; 
	printf("%d\n", (dp[70][0]-1+mod)%mod);
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值