正睿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;
}
### 关于OI集训资料和训练计划 #### 集训资料推荐 对于想要参与OI竞赛的学生来说,择合适的资料至关重要。以下是几类常见的OI集训资料: 1. **书籍** 经典的算法书如《挑战程序设计竞赛》[^1] 和《算法导论》可以作为理论基础的学习材料。这些书籍涵盖了据结构、图论以及动态规划等内容。 2. **在线课程与平台** 可以利用一些知名的编程学习网站来提升自己的技能水平,比如洛谷、Codeforces等。通过解决平台上不同难度级别的题目,逐步积累经验并掌握各种算法技巧[^2]。 3. **历年真题解析** 学习过往比赛中的经典试题及其解答方法非常重要。例如,在提到的一篇关于2021年牛客OI赛前集训营的文章中指出,“单Dijkstra的时间复杂度为 \(O((n+m)\log n)\)” 这一知识可以帮助理解最短路径问题的有效求解方式。 4. **总结文章** 来自其他参赛者的经验和教训同样宝贵。“OI集训总结”分享了一位手的心路历程:“为了不辜负这四年的OI学习生涯”,表达了坚持到底的决心;而另一份针对普及组的比赛总结则强调了实际操作过程中需要注意的具体事项,像“准备一个输入用的s组,还有ans组”的细节处理[^3]。 #### 训练计划建议 制定合理的训练计划能够帮助更好地备战OI赛事: - **短期目标设定**: 如果距离式考试仅剩下两个月时间,则需集中精力弥补短板领域的同时巩固强项。考虑到“文化课落了很多”,可能需要合理分配每天用于ACM/OI练习与其他学业复习之间的时间比例。 - **专项突破**: 对薄弱环节进行针对性强化训练。如果发现自己在某些特定类型的题目上总是遇到困难(如贪心策略或者网络流),就应该多花些功夫去研究这类问题的特及常用解决方。 - **拟实战演练**: 定期参加线上或线下的拟测试活动,熟悉考场环境氛围,并检验自己当前的真实竞技状态如何调整后续备考方向。 ```c++ #include <stdio.h> int main(){ long long n; scanf("%lld",&n); printf("%lld", n&1?(n>>1)*((n>>1)+1):(n>>1)*(n>>1)-1 ); return 0; } ``` 上述C++代码片段展示了T2提字的一个实现例子[^4],从中可以看出简洁高效的编码风格也是成功完成任务的关键因素之一. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值