正睿2018省选联测round8t1(dp)

探讨两人轮流从多堆石子中取石的游戏策略,分析针老师如何通过删除特定数量的石子堆来确保胜利,采用动态规划解决复杂度问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【问题描述】

针⽼师和⼩ C 在玩传统的取⽯⼦游戏,他们有 n 堆⽯⼦,第 i 堆⽯⼦
有 ai 个,两个⼈轮流操作,每次操作需要选择⼀堆⾮空的⽯⼦,然后取⾛
这堆⽯⼦⾥⾄少 1 颗⽯⼦,不能操作的⼈输,⼩ C 先⼿

现在针⽼师得到了⼀次作弊的机会:在游戏开始之前他可以动⽤膜法
删除某 k 堆⽯⼦,要求 k 是 d 的倍数,且 0 ≤ k < n。

针⽼师想知道,他有多少种作弊的⽅案,使得作弊后他⼀定必胜。

你只需要输出⽅案数对 109+710^9 + 7109+7 取模的值。

【输入格式】

第⼀⾏两个正整数 n, d。
第⼆⾏ n 个正整数,第 i 个正整数表⽰ ai

【输出格式】

输出⽅案数对 109+710^9 + 7109+7 取模的值。

【样例输入】

5 2
1 2 3 1 1

【样例输出】

4

【数据范围】

对于前 30% 的数据,有 1≤n≤201 ≤ n ≤ 201n20
对于前 50% 的数据,有 1≤n≤50,1≤ai≤1041 ≤ n ≤ 50,1 ≤ a_i ≤ 10^41n501ai104
另有 20% 的数据,满⾜不同的 ai 最多只有 5 种。
对于 100% 的数据,有 1≤n≤5∗105,1≤d≤10,1≤ai≤106,1≤∑i=1nai≤107。1 ≤ n ≤ 5 ∗ 10^5,1 ≤ d ≤ 10,1 ≤ ai ≤ 10^6, 1 ≤ ∑_{i=1}^n a_i ≤ 10^7。1n51051d101ai1061i=1nai107


solutionsolutionsolution:
朴素dpdpdpf[i][j][k]f[i][j][k]f[i][j][k]表示考虑了前iii个,选了x%d=jx\% d=jx%d=j个元素,异或和为kkk的方案数。
复杂度O(ndmax⁡{ai})O(nd \max\{a_i\})O(ndmax{ai})
考虑优化,注意到一个重要的性质∑iai≤107\sum_ia_i\le 10^7iai107

我们考虑将{ai}\{a_i\}{ai}降序排序,考虑对于第iii个元素,找到最小的lll满足2l&gt;ai2^l&gt;a_i2l>ai
那么我们知道此时的f[i][j][k]f[i][j][k]f[i][j][k]中,如果k&gt;=2lk&gt;=2^lk>=2l,那么必定是转移不到000的,就有一个剪枝了

新复杂度?
g(l,r)g(l,r)g(l,r)表示值在[l,r][l,r][l,r]中的数的个数
那么复杂度为
O(d∗∑i=120cnt[2i−1,2i−1]∗2i)≈O(d∑i=120ai)O(d*\sum_{i=1}^{20}cnt[2^{i-1},2^i-1]*2^i)\approx O(d\sum_{i=1}^{20}a_i)O(di=120cnt[2i1,2i1]2i)O(di=120ai)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define rept(i,x) for(int i = linkk[x],y = e[i].y;i;i = e[i].n,y = e[i].y)
#define P pair<int,int>
#define Pil pair<int,ll>
#define Pli pair<ll,int>
#define Pll pair<ll,ll>
#define pb push_back 
#define pc putchar
#define mp make_pair
#define file(k) memset(k,0,sizeof(k))
#define ll long long
int rd()
{
	int num = 0;char c = getchar();bool flag = true;
	while(c < '0'||c > '9') {if(c == '-') flag = false;c = getchar();}
	while(c >= '0' && c <= '9') num = num*10+c-48,c = getchar();
	if(flag) return num;else return -num;
}
const int p = 1e9+7;
int n,d,to;
int a[501000],bin[30];
int f[2][15][2010000];
inline void add(int &a,int b){a += b;if(a >= p) a -= p;}
int main()
{
	bin[0] = 1;rep(i,1,29) bin[i] = bin[i-1]<<1;
	n = rd();d = rd();
	rep(i,1,n) a[i] = rd(),to^=a[i];
	sort(a+1,a+n+1);reverse(a+1,a+n+1);	
	int mx = 29;
	while(bin[mx-1]>a[1]) mx--;
	f[0][0][to] = 1;
	rep(i,1,n)
	{
		if(bin[mx-1]>a[i]) mx--;
		rep(j,0,d-1) rep(k,0,bin[mx])
		{
			f[i&1][j][k] = 0;
	        add(f[i&1][j][k],f[!(i&1)][j?j-1:j+d-1][k^a[i]]);
	        add(f[i&1][j][k],f[!(i&1)][j][k]);
		}
	}
	printf("%d\n",f[n&1][0][0]-(n%d==0));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值