题解:Square Subsets

CF895C | 状压DP

Posted by TH911 on July 19, 2025

题目传送门

题意分析

发现一个神奇的东西:$a_i\leq70$。值域如此之小,一定可以利用。因此考虑与值域有关的做法。

寻找完全平方数,则其所有质因子的指数均为偶数

可以发现,$[1,70]$ 中的质数有且仅有 $19$ 个,可以考虑从此方面入手。

考虑影响是否为完全平方数的仅仅是质因子的指数的奇偶性,因此可以考虑状压 DP。

记第 $i$ 个质数为 $p_i,p_1=2$,设 $\textit{dp}{i,S}$ 表示选择完了 $a$ 中的 $1\sim i$,乘积的 $p_j$ 因子质数的奇偶性为 $S_j$ 时的方案数。则答案即 $\textit{dp}{n,\langle0,0,\cdots,0\rangle}$。注意到 $\vert S\vert\leq19$,显然可以压缩成一个二进制整数。

考虑如何递推。

设 $\textit{cnt}_i$ 为 $i$ 在 $a$ 中的出现次数,先考虑 $\textit{cnt}_i=1$ 的情况。

  • 若不选择 $i$,那么方案数为 $\textit{dp}_{i-1,S}$。
  • 否则,方案数为 $\textit{dp}_{i-1,S\oplus s}$。其中,$s$ 表示 $i$ 的状态。即对 $i$ 质因数分解,同时存储 $i$ 的质因子指数奇偶性。

则有:

\[\textit{dp}_{i,S}=\textit{dp}_{i-1,S}+\textit{dp}_{i-1,S\oplus s}\]

考虑 $\textit{cnt}_i\neq 1$ 的情况。

其实是一样的。$\textit{cnt}_i$ 个 $i$,选择的总方案数为 $2^{\textit{cnt}_i-1}$。

则有:

\[\textit{dp}_{i,S}=2^{\textit{cnt}_i-1}\left(\textit{dp}_{i-1,S}+\textit{dp}_{i-1,S\oplus s}\right)\]

注意到此时选择多少个 $i$ 对于奇偶性造成的影响并不需要考虑,因为那样仅仅是交换了一下 $\textit{dp}{i-1,S},\textit{dp}{i-1,S\oplus s}$ 而已。


注意到这样会 MLE,因此使用滚动数组优化掉前一维即可。

AC 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//#include<bits/stdc++.h>
#include<algorithm> 
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=1e5,Vprime=19,V=70,P=1e9+7;
constexpr const int prime[Vprime]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67};
int n,a[N+1],dp[2][1<<Vprime|1],cnt[V+1];
int qpow(int base,int n){
	int ans=1;
	while(n){
		if(n&1){
			ans=(ll)ans*base%P;
		}
		base=(ll)base*base%P;
		n>>=1;
	}
	return ans;
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		cnt[a[i]]++;
	}
	bool mode;
	dp[mode][0]=1;
	for(int i=1;i<=V;i++){
		if(!cnt[i]){
			continue;
		} 
		mode=!mode;
		int s=0;
		for(int x=i,j=0;j<Vprime;j++){
			while(x%prime[j]==0){
				s^=1<<j;
				x/=prime[j];
			}
		}
		int pl=qpow(2,cnt[i]-1);
		for(int j=0;j<(1<<Vprime);j++){
			dp[mode][j]=(ll)pl*(dp[!mode][j]+dp[!mode][j^s])%P;
		}
	}
	int ans=dp[mode][0]-1;
	if(ans<0){
		ans+=P; 
	}
	cout<<ans<<'\n';
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}