[ZROI159]取石子游戏 动态规划

  • 70pts
  • 就是求在一段序列中取出若干个数(不能全取完且取出的个数必须是d的倍数),使剩下的xor和为0的方案数
  • f[i][k][w]表示现在到了第i个数,取出的数的个数%d==k,没取出来的数xor和为0时方案数
  • 答案就是f[n][0][0]-(n%d==0) (不能全取完)
  • 对于另外20%的数据,发现所有数的本质不同的xor和最多有2^5个,预处理出来本质不同的xor和,在第三维枚举转移
  • 注意要使用滚动数组,每次要循环清零
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
const int mod=1e9+7;
int f[2][10][1<<20],a[2000000],b[10],c[1000],n,d,cnt=0,tot=0;
void upd(int &a,int b){a+=b; a-= a>=mod? mod:0;}
map<int,int> v;
void subtask1(){
	f[0][0][0]=1;
	rep(i,1,n){
		rep(k,0,d-1){
			rep(w,0,(1<<14)-1){
				f[i%2][k][w]=(f[i%2][k][w]+f[(i-1)%2][(k-1+d)%d][w])%mod;
				f[i%2][k][w^a[i]]=(f[i%2][k][w^a[i]]+f[(i-1)%2][k][w])%mod;
			}
		}
		rep(k,0,d-1)rep(w,0,(1<<14)-1)f[(i-1)%2][k][w]=f[(i-1)%2][(k-1+d)%d][w]=0;
	}
	cout<<f[n%2][0][0]-(n%d==0);
}
void work(int S){
	int now=1,tot=0;
	while(S){
		if(S&1)tot^=b[now];
		now++;S>>=1;
	}
	if(v[tot]==0) c[++cnt]=tot,v[tot]=1;
}
void subtask2(){
	rep(i,0,(1<<tot)-1)work(i);
	sort(c+1,c+cnt+1);
	f[0][0][0]=1;
	rep(i,1,n){
		rep(k,0,d-1){
			rep(j,1,cnt){
				int w=c[j]; 
				f[i%2][k][w]=(f[i%2][k][w]+f[(i-1)%2][(k-1+d)%d][w])%mod;
				f[i%2][k][w^a[i]]=(f[i%2][k][w^a[i]]+f[(i-1)%2][k][w])%mod;
			}
		}
		rep(k,0,d-1){
			rep(j,1,cnt){
				int w=c[j]; 
				f[(i-1)%2][k][w]=f[(i-1)%2][(k-1+d)%d][w]=0;
			}
		}
	}
	cout<<f[n%2][0][0]-(n%d==0);
}
int main()
{
	scanf("%d%d",&n,&d);
	rep(i,1,n){
		scanf("%d",&a[i]);
		if(v[a[i]]||tot>5)continue;
		b[++tot]=a[i],v[a[i]]=1;
	}
	v.clear();
	if(tot>5)subtask1();
	else subtask2();
	return 0;
}
  • 100pts
  • 考虑到序列的顺序和最后答案无关,把a从小到大排序,每次找到第一个(2^x)>=a[i],枚举0~2^(x+1)
  • 因为\sum a[i]<=10^7所以复杂度是对的
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
const int mod=1e9+7;
int f[2][10][1<<21],a[2000000],n,d;
void upd(int &a,int b){a+=b; a-= a>=mod? mod:0;}
int main()
{
	//freopen("a.in","r",stdin);
	scanf("%d%d",&n,&d);
	rep(i,1,n)scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	f[0][0][0]=1;
	rep(i,1,n){
		int now=1;
		while(now<=a[i])now<<=1;
		rep(k,0,d-1){
			rep(w,0,now){
				upd(f[i%2][k][w],f[(i-1)%2][(k-1+d)%d][w]);
				upd(f[i%2][k][w^a[i]],f[(i-1)%2][k][w]);
			}
		}
		rep(k,0,d-1)rep(w,0,now)f[(i-1)%2][k][w]=f[(i-1)%2][(k-1+d)%d][w]=0;
	}
	cout<<f[n%2][0][0]-(n%d==0);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值