codeforces895C 2000分状压

题目传送门

题意:

\dpi{150}n 个数,第 i 个数是 a_i 。

从这些数中选出若干个数,这些数的乘积需要是平方数。问有多少种方案。

不能选0个数,且不同方案需要两个方案中至少有一个数的下标不同。

数据范围:1\leqslant n\leqslant 10^5  , 1\leqslant a_i\leqslant 70 。

题解:

不超过 70 的质数有 19 个,考虑状压。

19 位二进制数 j ,每位的 0 代表当前位对应质数出现偶数次,每位的 1 代表当前位对应质数出现奇数次。

dp[i][j] 表示数值大小范围 [1,i] 中选择若干个数的质因子状压为 j 的方案数。

i 不超过 70 ,j 是 10^6 级别。不会超时,但是会MLE,滚动数组即可。

感受:

最近不在状态,也可能是本身弱,写题很不顺。

代码:

#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 1e6 + 5 ;
const ll mod = 1e9 + 7 ;
int n , a[maxn] , num[80] ;
int cnt = 0 , p[30] ;
ll two[maxn] ;
ll dp[2][maxn] ;
int su(int x)
{
	for(int i = 2 ; i * i <= x ; i ++)
	  if(x % i == 0)  return 0 ;
	return 1 ;
}
int ji(int x)
{
	int y = 1 ;
	int ans = 0 ;
	for(int i = 1 ; i <= 19 ; i ++)
	{
		int num = 0 ;
		while(x % p[i] == 0)
		{
			num ^= 1 ;
			x /= p[i] ;
		}
		if(num)  ans += y ;
		y <<= 1 ;
	}
	return ans ;
}
int main()
{
	scanf("%d" , &n) ;
	memset(num , 0 , sizeof(num)) ;
	memset(dp , 0 , sizeof(dp)) ;
	for(int i = 1 ; i <= n ; i ++)  
	{
		int x ;
	    scanf("%d" , &x) , num[x] ++ ;	
	}
	for(int i = 2 ; i <= 70 ; i ++)
	  if(su(i))  p[++ cnt] = i ;
	two[0] = 1ll ;
	for(int i = 1 ; i <= 1e5 ; i ++)  two[i] = two[i - 1] * 2ll % mod ;
	dp[1][0] = 1ll * two[num[1]] % mod ;
	for(int i = 2 ; i <= 70 ; i ++)
	{
		int x = ji(i) ;
		int y = 0 ;
		int z = max(0 , num[i] - 1) ;
		for(int j = 0 ; j < (1 << 19) ; j ++)
		{
		   if(num[i] >= 1)
		     dp[i % 2][x ^ j] += two[z] * dp[1 - (i % 2)][j] % mod ;
		   dp[i % 2][y ^ j] += two[z] * dp[1 - (i % 2)][j] % mod ;
		   dp[i % 2][x ^ j] %= mod ;
		   dp[i % 2][y ^ j] %= mod ;	
		} 
		memset(dp[1 - (i % 2)] , 0 , sizeof(dp[1 - (i % 2)])) ;
	}
	printf("%lld\n" , (dp[0][0] - 1ll + mod) % mod) ;
	return 0 ;
}
发布了215 篇原创文章 · 获赞 12 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Irving0323/article/details/104119551