CF895C Square Subsets(状压dp&组合数学)

Posted Harris-H

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF895C Square Subsets(状压dp&组合数学)相关的知识,希望对你有一定的参考价值。

CF895C Square Subsets(状压dp&组合数学)

平方数的一个性质是:所有质因子都出现偶数次。

a i ≤ 70 a_i \\le 70 ai70,小于 70 70 70的质因子只有 19 19 19个。

考虑把质因子当作状态,出现偶数次记为0,奇数次记为1。

因为权值很小,考虑以权值进行递推,令 d p [ i ] [ s ] dp[i][s] dp[i][s]。表示对于选择值域在 [ 1 , i ] [1,i] [1,i]状态为 s s s​的方案数。

初始化 d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1

预处理每个值出现的次数: c n t i cnt_i cnti

对于当前第 i i i个值 。

若选择偶数个,对于质因子的奇偶性无影响。

可以选择 C c n t i 0 + C c n t i 2 + C c n t i 4 ⋯ = 2 c n t i − 1 C_{cnt_i}^0+C_{cnt_i}^2+C_{cnt_i}^4\\dots =2^{cnt_i-1} Ccnti0+Ccnti2+Ccnti4=2cnti1

d p [ i ] [ s ] + = d p [ i − 1 ] [ s ] × 2 c n t i − 1 dp[i][s]+=dp[i-1][s]\\times 2^{cnt_i-1} dp[i][s]+=dp[i1][s]×2cnti1

若选择奇数个,对于质因子有影响。

同理 C c n t i 1 + C c n t i 3 + C c n t i 5 ⋯ = 2 c n t i − 1 C_{cnt_i}^1+C_{cnt_i}^3+C_{cnt_i}^5\\dots =2^{cnt_i-1} Ccnti1+Ccnti3+Ccnti5=2cnti1

d p [ i ] [ s ⊕ k ] = d p [ i − 1 ] [ s ] × 2 c n t i − 1 dp[i][s\\oplus k]=dp[i-1][s]\\times 2^{cnt_i-1} dp[i][sk]=dp[i1][s]×2cnti1

答案就是: d p [ m x ] [ 0 ] − 1 dp[mx][0]-1 dp[mx][0]1

这个 − 1 -1 1 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]​ 空集。

因为每个状态由前一个状态转移 ,所以可以滚动优化空间。

然后可以预处理 2 x 2^{x} 2x 优化时间。

时间复杂度: O ( m x 2 19 ) O(mx 2^{19}) O(mx219)

#include<bits/stdc++.h>
#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;
}

另外此题,把每个数转换为他们的状态后,题意就相当于就选择集合使得异或和为0的子集个数。

这个可以用线性基做,因为线性基外的所有数都可以通过线性基得到。

所以他们异或和都是为0的,这与答案的关系是一对一映射的。

所以我们可以把这些状态插入线性基,最后答案就是线性基外所有的数组成的集合。

a n s = 2 n − c n t − 1 ans=2^{n-cnt}-1 ans=2ncnt1​,减1是减去空集。

时间复杂度: O ( 20 n ) O(20n) O(20n)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 1e5;
const int P = 1e9 + 7;
using namespace std;
int n,a[N + 5],prime[N + 5],pcnt,vis[N + 5],val[N + 5],p[N + 5],cnt;
int mypow(int a,int x){int s = 1;for (;x;x & 1 ? s = 1ll * s * a % P : 0,a = 1ll * a * a % P,x >>= 1);return s;}
void prework()
{
    for (int i = 2;i <= 70;i++)
    {
        if (!vis[i])
            prime[++pcnt] = i;
        for (int j = 1;prime[j] * i <= 70 && j <= pcnt;j++)
        {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
}
void ins(int x)
{
    for (int i = 22;i >= 0;i--)
        if (x >> i & 1)
        {
            if (!p[i])
            {
                p[i] = x;
                cnt++;
                break;
            }
            x ^= p[i];
        }
}
int main()
{
    scanf("%d",&n);
    for (int i = 1;i <= n;i++)
        scanf("%d",&aCodeforces 895C - Square Subsets

Codeforces Round #448 (Div. 2)C. Square Subsets

cf660E Different Subsets For All Tuples

hdu 6125 Free from square (状压DP+分组背包)

Square Root of Permutation - CF612E

CF16E Fish(状压+期望dp)