20200520!hz【异或和为0的最大集合,powerful number求积性函数,相等边集容斥+限制异或和】

T1:石子游戏

给出 n n n个数 a i a_i ai,求删去最少的数,使得剩下的数的异或和为0。
A = m a x ( a i ) A=max(a_i) A=max(ai) n , A ≤ 500000 n,A\le500000 n,A500000

题目分析:

求异或和为0的最大集合不好直接求,转化为补集问题:求异或和为 ⊕ a i \oplus a_i ai的最小集合。

f [ i ] [ x ] f[i][x] f[i][x]表示选 i i i个数,能否取得异或和为 x x x,根据异或(线性基)的性质,可以发现最多只需要选 log ⁡ A \log A logA个数就可以取得所有可能的异或和。

选数的过程可以FWT优化,复杂度 O ( n log ⁡ 2 A ) O(n\log^2A) O(nlog2A),利用单点IFWT( I F W T ( a ) i = 1 n ∑ j ( − 1 ) d ( i & j ) a j IFWT(a)_i=\frac 1{n}\sum_{j}(-1)^{d(i\&j)}a_j IFWT(a)i=n1j(1)d(i&j)aj)可以每次 O ( A ) O(A) O(A)check,优化到 O ( n log ⁡ A ) O(n\log A) O(nlogA)

Code:

#include<bits/stdc++.h>
#define maxn 550005
using namespace std;
const int mod = 1e9+7;
int n,a[maxn],b[maxn],A,sum,bit[maxn];
int upd(int x){return x>=mod?x-mod:x;}
void FWT(int *a,int len){
	for(int i=2,l=1;i<=len;l=i,i<<=1)
		for(int j=0;j<len;j+=i)
			for(int k=j;k<j+l;k++){
				int u=a[k],v=a[k+l];
				a[k]=upd(u+v),a[k+l]=upd(u-v+mod);
			}
}
bool check(){
	int ret=0;
	for(int i=0;i<A;i++) ret=(ret+(bit[sum&i]&1?-1:1)*b[i])%mod;
	return ret!=0;
}
int main()
{
	scanf("%d",&n);
	for(int i=1,x;i<=n;i++) scanf("%d",&x),A=max(A,x),sum^=x,a[x]=1;
	A=A?1<<int(log2(A)+1):1;
	for(int i=1;i<A;i++) bit[i]=bit[i>>1]+(i&1);
	FWT(a,A),b[0]=1,FWT(b,A);
	for(int t=0;t<=20;t++){
		if(check()) return printf("%d\n",n-t),0;
		for(int i=0;i<A;i++) b[i]=1ll*b[i]*a[i]%mod;
	}
}

T2:函数

在这里插入图片描述
n ≤ 1 0 13 , k ≤ 20 n\le10^{13},k\le20 n1013,k20

题目分析:

直接Min_25筛跑 1 0 11 10^{11} 1011就已经很勉强了。
需要利用一个奇技淫巧:Powerful Number
两篇学习地址:zzq’s blogzjp-shadow

构造一个积性函数 G ( x ) G(x) G(x)使得 G ( p ) = F ( p ) G(p)=F(p) G(p)=F(p),并且 G ( x ) G(x) G(x)的前缀和可以快速求得。这道题中 G ( x ) = x k G(x)=x^k G(x)=xk

然后求出 H = F G H={\frac FG} H=GF,除法为狄利克雷除法,即有 F ( p q ) = ∑ i = 0 q G ( p i ) H ( p q − i ) F(p^q)=\sum_{i=0}^qG(p^i)H(p^{q-i}) F(pq)=i=0qG(pi)H(pqi) H H H也是
一个积性函数。

因为 F ( p ) = G ( p ) H ( 1 ) + G ( 1 ) H ( p ) F(p)=G(p)H(1)+G(1)H(p) F(p)=G(p)H(1)+G(1)H(p),而 H ( 1 ) = G ( 1 ) = 1 , G ( p ) = F ( p ) H(1)=G(1)=1,G(p)=F(p) H(1)=G(1)=1,G(p)=F(p),所以必然有 H ( p ) = 0 H(p)=0 H(p)=0,所以只有当 x x x的所有质因子次数都大于1时 H ( x ) H(x) H(x)才不为0(H(1)=1是特例).,这样的数就是Powerful Number,它一定可以表示为 a 2 b 3 a^2b^3 a2b3的形式,易得 n n n以内的powerful number个数 O ( ∑ i = 1 n ( n i 2 ) 1 3 ) = O ( n ) O(\sum_{i=1}^n(\frac n{i^2})^{\frac 13})=O(\sqrt n) O(i=1n(i2n)31)=O(n )的。

这有什么用呢?考虑原来的前缀和: ∑ i = 1 n F ( i ) = ∑ i j ≤ n H ( i ) G ( j ) = ∑ i = 1 n H ( i ) ∑ j = 1 n i G ( j ) \sum_{i=1}^nF(i)=\sum_{ij\le n}H(i)G(j)=\sum_{i=1}^nH(i)\sum_{j=1}^{\frac ni}G(j) i=1nF(i)=ijnH(i)G(j)=i=1nH(i)j=1inG(j)

这样我们就只需要算 i i i为powerful number时的值,于是复杂度就是 O ( n ∗ c a l c ( G ) ) O(\sqrt n*calc(G)) O(n calc(G))

怎么求 H H H?因为是积性函数,所以只需要求 H ( p i ) H(p^i) H(pi)

F ( p q ) = ∑ i = 0 q G ( p i ) H ( p q − i ) F(p^q)=\sum_{i=0}^qG(p^i)H(p^{q-i}) F(pq)=i=0qG(pi)H(pqi),构造关于指数的生成函数, G ( x ) G(x) G(x)对应的生成函数为 B ( x ) = ∑ i = 0 ∞ p i k x i B(x)=\sum\limits_{i=0}^\infty p^{ik}x^i B(x)=i=0pikxi F ( x ) F(x) F(x)对应的生成函数为 A ( x ) = 1 + ∑ i = 1 ∞ p k x i A(x)=1+\sum\limits_{i=1}^\infty p^kx^i A(x)=1+i=1pkxi,那么 H ( x ) H(x) H(x)对应的生成函数就应当是 A ( x ) B ( x ) \frac {A(x)}{B(x)} B(x)A(x),即 ( 1 + ∑ i = 0 ∞ p k x i ) ( 1 − p k x ) (1+\sum\limits_{i=0}^\infty p^kx^i)(1-p^{k}x) (1+i=0pkxi)(1pkx),观察可得第 t t t 项的系数为 p k − p 2 k p^k-p^{2k} pkp2k,于是 H ( p t ) = p k − p 2 k H(p^t)=p^k-p^{2k} H(pt)=pkp2k
更一般的,可以多项式求逆 O ( t log ⁡ t ) O(t\log t) O(tlogt)算出 H ( p t ) H(p^t) H(pt),当然也可以直接 O ( t 2 ) O(t^2) O(t2)递推(开个数组把算过的存下来,见LOJ6053)。

Code:

#include<bits/stdc++.h>
#define maxn 7000005
#define LL long long
using namespace std;
const int mod = 1e9+7;
LL n,a[maxn];
int sn,m,K,p[maxn/5],cnt,S[22][22],inv[22],pw[maxn],sum[maxn];
bool vis[maxn];
int calc(LL n,int k){
	int ret=0,fac=1,N=n%mod;
	for(int i=0;i<=k;i++){
		fac=1ll*fac*(N+1-i)%mod;
		ret=(ret+1ll*fac*S[k][i]%mod*inv[i+1])%mod;
	}
	return ret;
}
int ID(LL x){return x<=sn?x:m-n/x+1;}
void Powerful_Number(LL x,int k,int val){
	(sum[ID(n/x)]+=val)%=mod;
	for(int i=k;i<=cnt&&x<=n/p[i]/p[i];i++){
		LL pp=1ll*x*p[i];
		for(;pp<=n/p[i];pp*=p[i]) Powerful_Number(pp*p[i],i+1,1ll*(pw[i]-1ll*pw[i]*pw[i])%mod*val%mod); 
	}
}
int main()
{
	scanf("%lld%d",&n,&K),sn=sqrt(n);
	for(int i=S[0][0]=1;i<=K;i++) for(int j=1;j<=i;j++) S[i][j]=(S[i-1][j-1]+1ll*j*S[i-1][j])%mod;
	inv[0]=inv[1]=1; for(int i=2;i<=K+1;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(LL i=1;i<=n;i++) a[++m]=i=n/(n/i);
	for(int i=2;i<=sn;i++){
		if(!vis[i]) {p[++cnt]=i; for(int j=pw[cnt]=1;j<=K;j++) pw[cnt]=1ll*pw[cnt]*i%mod;}
		for(int j=1;j<=cnt&&(i*p[j]<=sn);j++) {vis[i*p[j]]=1;if(i%p[j]==0) break;}
	}
	Powerful_Number(1,1,1);
	int ans=0;
	for(int i=1;i<=m;i++) if(sum[i]) ans=(ans+1ll*sum[i]*calc(a[i],K))%mod;
	printf("%d\n",(ans+mod)%mod);
}

T3:画(画家小P)

在这里插入图片描述

题目分析:

集训队解题报告

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
DP总状态数是 O ( 3 n ) O(3^n) O(3n),转移时由于每次划分出的都是 l i m i t limit limit最小的点,所以 T T T必然只能出现 i i i的左边,于是转移的复杂度是 O ( ∑ i = 1 n 2 i − 1 ∗ 3 n − i ) = O ( 3 n ) O(\sum_{i=1}^n2^{i-1}*3^{n-i})=O(3^n) O(i=1n2i13ni)=O(3n)

对于 2 n 2^n 2n个子集算 F F F的复杂度论文里说的是 O ( n 2 l o g C ) O(n^2logC) O(n2logC),但实际上可以做到 O ( n l o g C ∗ 8 ) O(nlogC*8) O(nlogC8)
在这里插入图片描述
(zhengrui【20寒假Day 2】我覀,这个题是带修改的,每一位线段树维护,可以去掉第三维用多项式(两项)维护)

但是转移的时候要转进制找到0的集合,所以最后的复杂度是 O ( 3 n n + 2 n n log ⁡ C ) O(3^nn+2^nn\log C) O(3nn+2nnlogC)

Code:

#include<bits/stdc++.h>
#define maxn 16
#define LL long long
using namespace std;
const int mod = 998244353;
int n,m,id[maxn],rk[maxn],f[43046725],coef[1<<maxn],bit[1<<maxn],lg[1<<maxn],F[1<<maxn],inv2[65]={1,(mod+1)/2},pw[maxn],st[1<<maxn];
bool e[1<<maxn];
LL C,a[maxn];
bool cmp(int i,int j){return a[i]<a[j];}
int solve(int s){
	static LL b[maxn]; static int f[maxn][2][2]; int N=0; LL sum=0;
	for(int i=0;i<n;i++) if(s>>i&1) b[N++]=a[i],sum^=a[i];
	int ret=sum==C;
	for(int k=59;k>=0;k--){
		int x=0;
		for(int i=0;i<N;i++) x^=b[i]>>(k+1)&1;
		if(x^(C>>(k+1)&1)) break;
		memset(f,0,sizeof f);
		f[0][0][0]=1;
		for(int i=0;i<N;i++){
			x=b[i]>>k&1;
			for(int j=0;j<2;j++) for(int v=0;v<2;v++) for(int t=0;t<=x;t++)
				f[i+1][j^t][v|(t<x)]=(f[i+1][j^t][v|(t<x)]+1ll*(t==x?(b[i]&((1ll<<k)-1))+1:1ll<<k)%mod*f[i][j][v])%mod;
		}
		ret=(ret+1ll*f[N][C>>k&1][1]*inv2[k])%mod;
	}
	return ret;
}
int main()
{
	scanf("%d%d%lld",&n,&m,&C);
	for(int i=0;i<n;i++) scanf("%lld",&a[i]),id[i]=i,lg[1<<i]=i;
	sort(id,id+n,cmp),sort(a,a+n);
	for(int i=0;i<n;i++) rk[id[i]]=i;
	for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),x=rk[x-1],y=rk[y-1],e[1<<x|1<<y]=1;
	
	for(int i=0;i<n;i++) for(int s=0;s<1<<n;s++) if(s>>i&1) e[s]|=e[s^1<<i]; // if T \subset S is 1, S is 1. multidegree prefix.
	for(int s=1;s<1<<n;s++){
		coef[s]=!e[s];
		for(int t=(s-1)&s;t;t=(t-1)&s) if(t&(s&-s))
			coef[s]=(coef[s]-coef[t]*(!e[s^t]))%mod;
	}
	for(int s=1;s<1<<n;s++) if(!((bit[s]=bit[s>>1]+(s&1))&1)) coef[s]=1ll*coef[s]*(a[lg[s&-s]]%mod+1)%mod;
		
	for(int i=2;i<=60;i++) inv2[i]=1ll*inv2[i-1]*inv2[1]%mod;
	if(!C) F[0]=1;
	for(int s=1;s<1<<n;s++) F[s]=solve(s);
	
	int N=pw[0]=1;
	for(int i=1;i<=n;i++) pw[i]=N=N*3;
	for(int s=1;s<1<<n;s++) st[s]=st[s>>1]*3+(s&1);
	for(int s=1;s<1<<n;s++) if(bit[s]&1) st[s]+=pw[lg[s&-s]];
	
	f[0]=1; int ans=0;
	for(int s=0;s<N;s++) if(f[s]){
		int t=0,fp=0;
		for(int i=0;i<n;i++) if(s/pw[i]%3==0) t|=1<<i,!fp&&(fp=1<<i);
		if(t){
			int r=t^fp;
			for(int ns=r;;ns=(ns-1)&r){
				int now=s+st[fp|ns];
				f[now]=(f[now]+1ll*f[s]*coef[fp|ns])%mod;
				if(!ns) break;
			}
		}
		else{
			for(int i=0;i<n;i++) if(s/pw[i]%3==2) t|=1<<i;
			ans=(ans+1ll*f[s]*F[t])%mod;
		}
	}
	printf("%d\n",(ans+mod)%mod);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
求1~n数组的最大异或和也可以使用类似于求一段区间最大异或和的方法。具体步骤如下: 1. 将1~n数组中的所有数以二进制形式插入到字典树中。 2. 对于每个数,从高位到低位依次匹配字典树上的节点,如果当前位为1,就往字典树的右子树走,否则就往左子树走。匹配完整个二进制数后,我们可以得到一个最大异或值。 3. 对于1~n数组,我们可以将其中的相邻两个数看作一段区间,然后使用类似于求一段区间最大异或和的方法求出最大异或和。 时间复杂度为O(nlogC),其中n为数组长度,C为数的范围。以下是求1~n数组的最大异或和的C++代码: ```c++ #include <iostream> using namespace std; const int MAXN = 100010; const int MAXBITS = 30; struct TrieNode { int cnt; int children[2]; } trie[MAXN * MAXBITS]; int root, node_cnt; void insert(int x) { int p = root; for (int i = MAXBITS - 1; i >= 0; i--) { int idx = (x >> i) & 1; if (!trie[p].children[idx]) { trie[p].children[idx] = ++node_cnt; } p = trie[p].children[idx]; trie[p].cnt++; } } int query(int x) { int p = root, res = 0; for (int i = MAXBITS - 1; i >= 0; i--) { int idx = (x >> i) & 1; if (trie[trie[p].children[idx ^ 1]].cnt > 0) { res += (1 << i); p = trie[p].children[idx ^ 1]; } else { p = trie[p].children[idx]; } } return res; } int main() { int n; cin >> n; root = 1; node_cnt = 1; int pre = 0, ans = 0; for (int i = 1; i <= n; i++) { int x; cin >> x; insert(pre); pre ^= x; ans = max(ans, query(pre)); } cout << ans << endl; return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值