雅礼集训 1.2 取石子游戏

19 篇文章 0 订阅

取石子(stone)


【题目描述】
有 n 堆石子,第 i 堆有 xi 个。
Alice 和 Bob 轮流取石子(先后手未定),Alice 每次从一堆中取走 a 个,Bob
每次从一堆中取走 b 个,无法操作者输。
不难发现只会有四种情况:Alice 必胜;Bob 必胜;先手必胜;后手必胜。
你需要选定若干堆石子(共有 2^n 种方案),Alice 和 Bob 只能在你选出的堆
中取,问以上四种情况对应的方案数。
【输入数据】
第一行三个整数 n,a,b,第二行 n 个整数 x1~xn。
【输出数据】
一行四个整数,分别表示 Alice 必胜、Bob 必胜、先手必胜和后手必胜的方
案数,对 10^9+7 取模。
【样例输入】
2 2 3
2 3
【样例输出】
2 0 1 1
【样例解释】
选定空集时后手必胜,选定{2}时 Alice 必胜,选定{3}时先手必胜,选定{2,3}时 Alice
必胜。
【数据范围】
对于 10%的数据,n,xi<=5。
对于 50%的数据,n<=20。
对于另外 10%的数据,a=b。
对于又另外 20%的数据,a=1。
对于 100%的数据,1<=n<=100000,1<=a,b,xi<=10^9。


首先我们可以把每堆石子的个数mod(a+b)

这是从博弈学的平衡性(我自己取的名)入手的

下面是个人见解,正确性unknown

首先,这样想一场游戏,只有一堆石子

你取了a个,对方紧跟着你在同一堆取了b个,

一轮减少(a+b)个 ,其实无论经过多少轮结果都是一样的。

该输的照样输,该赢的照样赢

那么就把个数mod(a+b)不就好了?


如果有多堆的话,一人突然对另外一堆来操作

这时,另一人会紧跟前一人去同样的堆操作

且在前一人重新对原来的堆操作之前,也不对前一堆操作


这样,其实第一人只是将操作推后了而已,他还是要操作的。

那么,为啥另一人会跟着呢?

因为前一人换堆肯定是因为他不换就会输

后一人就会赢,所以后一人这样就可以将前一人锁定在必输的情形,这是出于最佳策略


就像那个多个游戏的和什么什么的。。。。。


然后对于所有的堆分类讨论即可,


::

stone:

不妨假设a<b。

每堆石子先对a+b取模,然后可以分为4种:

(1) xi<a,没用。

(2) a<=xi<b,只要存在则a必胜。

(3) b<=xi<2a,只和奇偶性有关。

(4) 2a<=xi,存在至少2个则a必胜,存在1个且(3)为偶数则先手必胜,存在1个且(3)为奇数则a必胜,不存在且(3)为奇数则先手必胜,不存在且(3)为偶数则后手必胜。


注意,在n个物品中选出奇数个物品的方案数为2^(n-1)

(或偶数个物品)

看看杨辉三角就知道 了。

另外这个题将无石子归为后手赢,相当于强行定义C(0,0)=1,这不仅让杨辉三角失去了朴素的美感,还让代码挤满了丑恶的三目运算符。

ACcode:

//神之技巧:ΣC(n,k)=2^(n-1) 其中k为都奇数,或都为偶数,可以从杨辉三角上直观的证明

/*


0:无关
1:b>=u>=a 有则a必胜 因为a可以把这堆屯着,b管不着
然后是重要的两点
I:如果2*a>b
那么一堆石子满足b<=u<2*a
双方只要有一个人拿了一次,就会使它废为第0类,成为争夺的重点,有奇数个则先手赢,否则后手赢
一堆石子满足 u>=2*a>b
那么A只要拿一次就可以把它转化为1然后胜利
所以B的唯一机会就是先手(搞掉唯一的一堆),
所以这样的石子有两堆则A必赢
有一堆则B可能赢(把这堆破坏会浪费B的先手条件,看奇偶)。
无则看奇偶
II:如果2*a<=b
那么a<=2*a<=b
无异于1
u>=b时看奇偶

*/

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define mod 1000000007
#define maxn 100005
#define LL long long
using namespace std;

LL n,a,b;
bool rev;

LL pow(int base,int k){
	LL ret=1;
	for(;k;k>>=1,base=1ll*base*base%mod) if(k&1) ret=ret*base%mod;
	return ret;
}

int main(){
	
	freopen("stone.in","r",stdin);
	freopen("stone.out","w",stdout);
	
	LL u,v;
	
	scanf("%lld%lld%lld",&n,&a,&b);
	
	if(a>b) swap(a,b),rev=1;
		
	LL cnt[5]={},ans[5]={};
	for(int i=1;i<=n;i++){
		scanf("%lld",&u);
		u%=(a+b);
		cnt[(u>=a) + (u>=b) + (u>=b && u>=2*a)]++;
	}
	
	LL inc=pow(2,cnt[0]);//最后计算
	ans[0]=((pow(2,cnt[1])-1)*pow(2,cnt[2]+cnt[3])%mod+(pow(2,cnt[3])-cnt[3]-1)*pow(2,cnt[2])%mod+cnt[3]*(cnt[2]?pow(2,cnt[2]-1):0)%mod)%mod;
	//ans[1]=0;
	ans[2]=((cnt[2]?pow(2,cnt[2]-1):0)+(cnt[2]?pow(2,cnt[2]-1):1)*cnt[3]%mod)%mod;
	ans[3]=(cnt[2]?pow(2,cnt[2]-1):1);
	
	for(int i=0;i<4;i++)
		ans[i]=(ans[i]*inc)%mod,
		ans[i]=(ans[i]+mod)%mod;
		
	if(rev) swap(ans[0],ans[1]);
	printf("%lld %lld %lld %lld\n",ans[0],ans[1],ans[2],ans[3]);
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值