雅礼集训1.2 T3取石子

文章描述了一个关于石子的游戏,Alice和Bob轮流从n堆石子中取走一定数量。通过模运算简化问题,分析了四种获胜情况:Alice必胜、Bob必胜、先手必胜、后手必胜。对于每种情况,文章给出了具体的策略和计数方法,如考虑石子数量对(a+b)的余数,以及不同数量范围内的石子对胜负的影响。

题目大意

nnn堆石子,第iii堆有xix_ixi个。AliceAliceAliceBobBobBob轮流取石子,先后手未定,AliceAliceAlice每次从一堆中取走aaa个,BobBobBob每次从一堆中取走bbb个,不能操作者输。

不难发现只有四种情况:AliceAliceAlice必胜,BobBobBob必胜,先手必胜,后手必胜。你需要选若干堆石子(总共有2n2^n2n种选法),AliceAliceAliceBobBobBob只能在你选出的堆中取,问以上四种情况对应的方案数。

1≤n≤1051\leq n\leq 10^51n1051≤a,b,xi≤1091\leq a,b,x_i\leq 10^91a,b,xi109


题解

不妨设a<ba<ba<b

首先,我们可以将每一堆的石子数量模(a+b)(a+b)(a+b)。为什么可以这样呢?

如果模(a+b)(a+b)(a+b)后可以证明一方必胜

  • 如果这一方是后手,则只要先手拿的那一堆大于等于(a+b)(a+b)(a+b),则取那一堆;否则按最优策略取对应的那一堆
  • 如果这一方是先手,则先按最优策略取一堆,然后它就是后手了,接着按后手的方法来取就行了

然后我们可以分类讨论,不妨设a<ba<ba<b

  • 如果xi<ax_i<axi<a,则这一类没有用
  • 如果a≤xi<ba\leq x_i<baxi<b,则存在则aaa必胜
  • 如果b≤xi<2ab\leq x_i<2abxi<2a,答案只与这一类的奇偶性相关
  • 如果xi≥2ax_i\geq 2axi2a,在没有第二类石子的条件下
    • 存在至少两个则aaa必胜
    • 存在一个且第三类石子个数为偶数则先手必胜
    • 存在一个且第三类石子个数为奇数则aaa必胜
    • 不存在且第三类石子个数为奇数则先手必胜
    • 不存在且第三类石子个数为偶数则后手必胜

时间复杂度为O(n)O(n)O(n)

code

#include<bits/stdc++.h>
using namespace std;
const int N=100000;
int n,a,b,fl=0,x[100005];
int v1=0,v2=0,v3=0,v4=0;
long long ans1=0,ans2=0,ans3=0,ans4=0,jc[N+5],ny[N+5];
long long mod=1000000007;
long long mi(long long t,long long v){
	if(!v) return 1;
	long long re=mi(t,v/2);
	re=re*re%mod;
	if(v&1) re=re*t%mod;
	return re;
}
void init(){
	jc[0]=1;
	for(int i=1;i<=N;i++) jc[i]=jc[i-1]*i%mod;
	ny[N]=mi(jc[N],mod-2);
	for(int i=N-1;i>=0;i--) ny[i]=ny[i+1]*(i+1)%mod;
}
long long C(int x,int y){
	return jc[x]*ny[y]%mod*ny[x-y]%mod;
}
int main()
{
	init();
	scanf("%d%d%d",&n,&a,&b);
	if(a>b){
		fl=1;swap(a,b);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&x[i]);
		x[i]=x[i]%(a+b);
		if(x[i]<a) ++v1;
		else if(x[i]>=a&&x[i]<b) ++v2;
		else if(x[i]>=b&&x[i]<2*a) ++v3;
		else ++v4;
	}
	long long tmp=mi(2,v1+v3+v4);
	for(int i=1;i<=v2;i++){
		ans1=(ans1+C(v2,i)*tmp%mod)%mod; 
	}
	tmp=mi(2,v1);
	for(int i=0;i<=v3;i++){
		if(i&1) ans3=(ans3+C(v3,i)*tmp%mod)%mod;
		else ans4=(ans4+C(v3,i)*tmp%mod)%mod;
	}
	tmp=mi(2,v1)*v4%mod;
	for(int i=0;i<=v3;i++){
		if(i&1) ans1=(ans1+C(v3,i)*tmp%mod)%mod;
		else ans3=(ans3+C(v3,i)*tmp%mod)%mod;
	}
	tmp=mi(2,v1+v3);
	for(int i=2;i<=v4;i++){
		ans1=(ans1+C(v4,i)*tmp%mod)%mod;
	}
	if(fl) swap(ans1,ans2);
	printf("%lld %lld %lld %lld",ans1,ans2,ans3,ans4);
	return 0;
}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值