Wannafly summer camp Day3 —— Stones
初次接触SG博弈,参考了很多大牛的博客,经历了痛并快乐的思考过程。嗯,虽然还是不懂SG到底是什么,嘤嘤嘤…
但博弈不就是一个确定的必赢必输规律吗?不用管什么SG了,照着规律来答案一定是对的~~ 忽然开心( •̀ ω •́ )y
下面开始我复杂的崎岖的解题过程,途中废话可能过多,必要可以手动屏蔽,向下滑… I wil show you the code(手动滑稽)
训练赛 Wannafly summer camp Day3 这套题,恕我直言——————太毒瘤啦!!!
队友被两道的签到题卡住,一个题会卡精度,一个题纯暴力打表找规律。。。
一个半小时过去了,爆0!!!
两水题无望,我开始选择开博弈。
进入正题:
这道题和以前做过的石子堆博弈类似,先考虑只有一个堆的情况,一个堆的初始石头数会直接决定先手胜利或失败,从中找到规律 。
以石头数从1开始,递增规律为:
1~b+a-1个一定是先手赢
之后a个一定是先手输
之后b个一定是先手赢
之后a个一定是先手输
之后b个一定是先手赢
…
规律浅显。(^-^)V
虽然找出来这个规律,但还不能高兴太早。。。后面再考虑上多个石头堆就复杂了。
具体大佬是怎么想的我不知道。。我是全部情况找出来的,对,没有算法,就是暴力枚举情况_(:з」∠)_
☆☆☆情况1:有一个石头堆数 a<= X <= b 就能直接通过拿这个堆获得胜利!!
☆☆☆情况2:石头堆都是先手输状态,先手必输!!! 不信自己玩玩看!!
☆☆☆情况3:只有一个石头堆赢,别的石头堆都输,先手必赢(就看准会赢那堆拿,拿完后下个人只能把必输的状态拿走,先手一直都能拿必赢态!!!)
☆☆☆情况4:能赢的石头堆有偶数个,,,emmmmm这时。。我就被打哭了,嗯。(捂脸)我是弟弟。
通过大佬的SG打表代码看出的规律:这里设置先声明一个变量 contribute_w,
简单理解为在赢的状态下,还能坚持几轮一直保持赢的状态,举个例子,如果这个堆现在有7个石头,最少拿2,最多拿3,通过之前的规律,这堆初始状态为必赢,但…这个石头堆可以不用拿2个(剩5)到下个必输态,玩家可以选择拿3个(剩4)保持这个堆的必赢态!!!contribute_w = 2(初始为1,能坚持一轮+1) 可这样有什么用呢?? 这个先手不会是个傻子吧。。给下家留必赢态!!!
好吧,再来加一个石头堆,就是只能拿一次就会到必输态那种。。。
现在有: 8 7
两个石头堆,最少拿2,最多拿3…现在看来,两个堆都是必赢态,先手的玩家拿完一个必赢,就成了后手一直拿必赢!!!这时先手不乐意了,觉得这种 有两个赢的状态 就是坑,于是为了把这坑留给对方,选择把7个石头的堆拿成4… (:з」∠) 好了,现在后手就必须面对这个必输的坑啦~~ (ps:现在的石头 8 4 他们的contribute_w都是 1 没办法再推给对方)
所以对 能赢的的状态 计算其contribute_w ,将得到值相互异或,看看是不是将所有堆赢的状态都轮换着拿,最后还能不能给先手剩下单独一个堆的必赢态,也就是异或值不为0 ,这样先手就能赢啦。反之, 先手输!
好了,下面只要把这些情况都考虑到,规律就有啦。代码实现其实很简单的,嘤嘤嘤…
AC代码:
/*
* @Author: Achan
* @Date: 2018-10-27 18:12:22
* @Last Modified by: Achan
* @Last Modified time: 2018-10-29 11:12:20
*/
#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;
#define X first
#define Y second
#define eps 1e-2
#define gcd __gcd
#define pb push_back
#define PI acos(-1.0)
#define lowbit(x) (x)&(-x)
#define fin freopen("in.txt","r",stdin);
#define fout freopen("out.txt","w",stdout);
#define bug printf("!!!!!\n");
#define mem(x,y) memset(x,y,sizeof(x))
#define rep(i,j,k) for(int i=j;i<(int)k;i++)
#define per(i,j,k) for(int i=j;i<=(int)k;i++)
#define pset(x) setiosflags(ios::fixed)<<setprecision(x)
#define io std::ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
typedef long long ll;
typedef long double LD;
typedef pair<int,int> pii;
typedef unsigned long long ull;
const int inf = 1<<30;
const ll INF = 1e18 ;
const int mod = 1e9+7;
const int maxn = 1e5+2;
const int mov[4][2] = {-1,0,1,0,0,1,0,-1};
const int Mov[8][2] = {-1,-1,-1,0,-1,1,0,-1,0,1,1,-1,1,0,1,1};
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int main(void)
{
//fin
//fout
io
int T;cin>>T;
while(T--)
{
int n,a,b;
cin>>n>>a>>b;
int flag = 0;
int ans = 0;
while(n--)
{
int t;
cin>>t;
if(flag) continue; //已确定先手赢,跳过
if(a<=t && t<=b) flag = 1; //确定先手赢
if(t <= b) continue; //必输不统计
else if( b<t && t <= a+b-1)
{
ans ^= 1; //该堆下次必须让对方输!! 胜利贡献1
}
else
{
if(a==1) ans ^= t%(a+b); //特殊处理a=1 ,胜利贡献一定是 0~a+b-1的循环!!
else
{
// a个1 + a个0 + (从b开始的相对长度)/a : 2 ~ (a+b-1)/a 的循环
t -= b;
int np = t % (a+b);
int conw = np/a; //贡献胜利
//cout<<conw << endl;
//特殊处理前a个1 a个0 取反即可
if(conw == 1) conw = 0;1范围:说明在a里面 不做贡献
else if(!conw) conw = 1; //在0范围:说明是对上次的额外贡献 1
ans ^= conw; //异或 胜利贡献次数 更新最终SG
}
}
// sum += ( (np>=1 && np<=b)? 1:0);
}
puts((ans || flag)?"Yes":"No");
}
}