CF895C Square Subsets(状压dp)

CF895C Square Subsets(状压dp)

时间限制
4.00s
内存限制
250.00MB
judge:点我跳转

题目描述

Petya was late for the lesson too. The teacher gave him an additional task. For some array a a Petya should find the number of different ways to select non-empty subset of elements from it in such a way that their product is equal to a square of some integer.

Two ways are considered different if sets of indexes of elements chosen by these ways are different.

Since the answer can be very large, you should find the answer modulo 1 0 9 + 7 10^{9}+7 109+7 .

输入格式

First line contains one integer n ( 1 < = n < = 1 0 5 ) n ( 1<=n<=10^{5} ) n(1<=n<=105) — the number of elements in the array.

Second line contains n n integers a i ( 1 < = a i < = 70 ) a_{i} ( 1<=a_{i}<=70 ) ai(1<=ai<=70) — the elements of the array.

输出格式

Print one integer — the number of different ways to choose some elements so that their product is a square of a certain integer modulo 1 0 9 + 7 10^{9}+7 109+7 .

题意翻译

Petya又迟到了…老师给了他额外的任务。对于一些数组a,Petya需要找到从中间选择非空子集,使它们的乘积等于某个整数的平方的方法的数量。 如果这些方法所选择的元素的索引不同,则认为这两种是不同的方法。 因为结果可能很大,结果需要 m o d    1 0 9 + 7 mod \; 10^9+7 mod109+7

感谢@KriaeTh 提供的翻译

输入输出样例

输入 #1

4
1 1 1 1

输出 #1

15

输入 #2

4
2 2 2 2

输出 #2

7

输入 #3

5
1 2 4 5 8

输出 #3

7

说明/提示

In first sample product of elements chosen by any way is 1 1 1 and 1 = 1 2 1=1^{2} 1=12 . So the answer is 2 4 − 1 = 15 2^{4}-1=15 241=15 .

In second sample there are six different ways to choose elements so that their product is 4 4 4 , and only one way so that their product is 16 16 16 . So the answer is 6 + 1 = 7 6+1=7 6+1=7 .

题解


转载自:作者: pmt2018 更新时间: 2019-01-28 17:45

在Ta的博客查看


一道状压 d p dp dp 好题。

注意到此题 a i ≤ 70 a_i\leq70 ai70 , 并且一个数是否为完全平方数只与它的质因子奇偶性有关,所以 n ≤ 1 0 5 n\leq10^5 n105 是废话,我们只需要用一个桶统计每个数出现的个数就好了。

另外,打表发现 70 70 70 以内质数只有 19 19 19 个,所以想到状压 d p dp dp

d p i , m a s k dp_{i,mask} dpi,mask 表示考虑到 1 − 70 1-70 170 中的第 i i i 个数。并且因为一个数是否为完全平方数只与它的质因子奇偶性有关, m a s k mask mask 表示这 19 19 19 个数的奇偶性,若第 j j j 个质数的因数个数为奇数则 m a s k mask mask j j j 位为 1 1 1

考虑 d p dp dp 方程的转移。

先从 1 − 70 1-70 170 枚举 i i i ,若 c n t i cnt_i cnti (即数i在数组中出现次数)不为零,则枚举 m a s k mask mask
显然,当选奇数个数 i i i 时, m a s k mask mask 会发生改变,我们将 i i i 分解质因数, m a s k mask mask 变为 m a s k 1 , d p i , m a s k 1 mask1,dp_{i,mask1} mask1,dpi,mask1 可以从 d p i − 1 , m a s k dp_{i-1,mask} dpi1,mask 转移。

当选偶数个 i i i 时, m a s k mask mask 不会改变, d p i , m a s k dp_{i,mask} dpi,mask 可以从 d p i − 1 , m a s k dp_{i-1,mask} dpi1,mask 转移。

让我们令当前的 c n t i = k cnt_i= k cnti=k ,选奇数个的情况 C k 1 + C k 3 + C k 5 + ⋅ ⋅ ⋅ = ∑ i ≤ k , i ≡ 1 ( m o d 2 ) i C k i = 2 k − 1 C_k^1+C_k^3+C_k^5+\cdot\cdot\cdot=\sum\limits_{i\leq k,i\equiv1(mod2)}^iC_k^i=2^{k-1} Ck1+Ck3+Ck5+=ik,i1(mod2)iCki=2k1

选偶数个的情况有 C k 0 + C k 2 + C k 4 + ⋅ ⋅ ⋅ = ∑ i ≤ k , i ≡ 0 ( m o d 2 ) i C k i = 2 k − 1 C_k^0+C_k^2+C_k^4+\cdot\cdot\cdot=\sum\limits_{i\leq k,i\equiv0(mod2)}^iC_k^i=2^{k-1} Ck0+Ck2+Ck4+=ik,i0(mod2)iCki=2k1

这两个式子可以用二项式定理证明。

所以 d p i , m a s k 1 + = d p i − 1 , m a s k ∗ 2 k − 1 dp_{i,mask1}+=dp_{i-1,mask}*2^{k-1} dpi,mask1+=dpi1,mask2k1

如果 c n t i = = 0 cnt_i==0 cnti==0 我们就将 d p i − 1 dp_{i-1} dpi1 赋值给 d p i dp_{i} dpi

我们可以看到, d p i , m a s k dp_{i,mask} dpi,mask 只需从 d p i − 1 dp_{i-1} dpi1 中的数转移,我们可以用滚动数组滚掉一维。

这样我们就做完了。

注意开 l o n g l o n g long long longlong

代码

#include
#define mp make_pair
#define pb push_back

#define y0 pmt
#define y1 pmtpmt
#define x0 pmtQAQ
#define x1 pmtQwQ

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int > vi;
typedef pair<int, int > pii;
typedef vector<pii> vii;
const int inf = 0x3f3f3f3f, maxn = 100007, mod = 1e9 + 7;
const ll linf = 0x3f3f3f3f3f3f3f3fLL;
const ll P = 19260817;
const int p[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 };//质数表
int n;
ll dp[2][1 << 19];
ll cnt[75];
int I = 0;
ll h[maxn];//2^k的表
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		int rt;
		scanf("%d", &rt);
		cnt[rt]++;
	}
	h[0] = 1;
	dp[0][0] = 1;
	for (int i = 1; i <= n; i++)h[i] = (h[i - 1] << 1) % mod;
	for (int i = 1; i <= 70; i++) {
		if (cnt[i] == 0)continue;
		I ^= 1;//滚动数组
		memset(dp[I], 0, sizeof(dp[I]));//注意初始化
		for (int mask = 0; mask < (1 << 19); mask++) {
			int mask1 = mask;
			int x = i;
			for (int j = 0; j < 19 && x >= p[j]; j++) {
				while (x%p[j] == 0)x /= p[j], mask1 ^= (1 << j);
			}
			(dp[I][mask1] += 1LL * dp[I ^ 1][mask] * h[cnt[i] - 1] % mod) %= mod;
			(dp[I][mask] += 1LL * dp[I ^ 1][mask] * h[cnt[i] - 1] % mod) %= mod;
		}
	}
	printf("%I64d\n", (dp[I][0] - 1 + mod) % mod);
	return 0;
}

你可能感兴趣的:(dp-动态规划,状压dp)