D. Tokitsukaze, CSL and Stone Game
Tokitsukaze, CSL and Stone Game
题意:
有n堆石头,每次只能选择一堆取其一个
如果取不了石头,呢么就输了
如果取完后出现两堆一样多,呢么就输了
解析:
有三种特殊情况要特判
1.一开始就出现两个0
2.出现大于1次,两个一样的,或者超过2次一样的
3.a[i]+1==a[i+1],a[i+1]==a[i+2]
其他的都可以化为:a[i]=min(a[i],a[i-1]+1);
sum+=a[i]-min(a[i],a[i-1]+1);
判断sum的奇偶,为奇sjfnb,为偶sjfnb
#include<bits/stdc++.h>
#define ll long long
#define MAXN 100005
using namespace std;
int a[MAXN];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+1+n);
int num=0;
for(int i=1;i<=n-1;i++)
{
if(a[i]==a[i+1])
{
num++;
if(num>=2||a[i]==0)//相等的>2获得2对的的大于2,直接输
{ //开始就两个0,直接输//
printf("cslnb\n");
return 0;
}
}
}
for(int i=1;i<=n-2;i++)
{
if(a[i]+1==a[i+1]&&a[i+1]==a[i+2])//前面一个顶住后面两个,怎么变都是输
{
printf("cslnb\n");
return 0;
}
}
ll sum=0;
for(int i=1;i<=n;i++)//直接求从0开始的变化次数,偶数的cslnb
sum+=a[i]-(i-1);
printf("%s\n",sum%2==0?"cslnb":"sjfnb");
return 0;
}
题意:
有n个石子,每次取1个,2个,k个,谁不能取,谁输
解析:
我们用sg函数打表,然后找规律
ac:
#include<bits/stdc++.h>
#define N 2
#define MAXN 1005
using namespace std;
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void getSG(int n)
{
int i,j;
int sum=0;
memset(SG,0,sizeof(SG));
//因为SG[0]始终等于0,所以i从1开始
for(i = 1; i <= n; i++){
//每一次都要将上一状态 的 后继集合 重置
memset(S,0,sizeof(S));
for(j = 0; f[j] <= i && j <= N; j++)
S[SG[i-f[j]]] = 1; //将后继状态的SG函数值进行标记
for(j = 0;; j++) if(!S[j]){ //查询当前后继状态SG值中最小的非零值
sum++;
SG[i] = j;
break;
}
}
}
void solve(int n,int k)
{
if(k%3!=0)
{
if(n%3==0)
printf("Bob\n");
else
printf("Alice\n");
}
else{
if(k==3)
{
if(n%4==0)
printf("Bob\n");
else
printf("Alice\n");
}
else{
n%=(k+1);
if(n!=k&&n%3==0)
printf("Bob\n");
else
printf("Alice\n");
}
}
}
int main()
{
int t,n,k;
cin>>t;
while(t--)
{
cin>>n>>k;
solve(n,k);//getSG(n),判断SG[n],这里n很大,我们要打表找规律
}
return 0;
}
https://acm.ecnu.edu.cn/contest/196/problem/A/
Cuber QQ 和 Little Fang 两人会按照游戏规则轮流写 { 1,2,⋯,N } ( N 是一个正整数)中的一个数。
游戏的规则是这样的,若一个人写下数 i , 则另一个人只能写 i+1 或 2i ( i,i+1,2i 均不超过 N )。两个人中,谁先写到 N 这个数字,谁就能获胜。
当然 Cuber QQ 为了表现自己的绅士,他让 Little Fang 先写, Little Fang 的开场是单调而固定的,他一定会写数字 1 。
由于表演需要,两个人一共要玩 T 局游戏。每局游戏都会给定提前正整数 N ,当然 Cuber QQ 和 Little Fang 的聪明程度是毋庸置疑的,所以他们都会按照最优的策略进行游戏。
你能预言他们两个人游戏的结局吗?
解析:
有向图博弈论,数据范围非常大,我们先打表
打表:
#include<bits/stdc++.h>
#define MAXN 1005
using namespace std;
int to[MAXN<<1],nxt[MAXN<<1],head[MAXN<<1];
int tot=0;
int sg[MAXN<<1];
void init()
{
tot=0;
memset(head,0,sizeof(head));
memset(sg,-1,sizeof(sg));
}
void add(int u,int v)
{
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
int getsg(int x)
{
if(sg[x]!=-1) return sg[x];//非常重要的剪枝
int vis[MAXN];
memset(vis,0,sizeof(vis));
for(int i=head[x];i;i=nxt[i])
{
int v=to[i];
sg[v]=getsg(v);
vis[sg[v]]=1;
}
for(int i=0;;i++)
if(vis[i]==0)
return i;
}
int main()
{
for(int j=1;j<=1000;j++)
{
init();
add(0,1);
for(int i=1;i<j;i++)//构图
{
add(i,i+1);
if(i*2!=i+1&&i*2<=j)
add(i,i*2);
}
if(getsg(0)==0)//从0出发
printf("%d\n",j);
}
return 0;
}
发现先手必败的为奇数位二进制位不能有1
ac:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int get2(ll x)
{
int v=0,a[100];
while(x)
{
a[++v]=x%2;
x=x/2;
}
for(int i=1;i<=v;i++)
{
if(i%2==1)
{
if(a[i]==1)
return 0;
}
}
return 1;
}
int main()
{
ll n;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%lld",&n);
int k=get2(n);
if(k==0)
printf("Little Fang Win\n");
else
printf("Cuber QQ Win\n");
}
return 0;
}
https://vjudge.net/problem/HDU-1524
题意:
给定一个有向图,图中的点上有若干个棋子,两个人一次移动一个棋子,棋子只能向有向边的方向移动,谁无法移动谁输
有向图sg博弈,起点多个,一个nim游戏,求各个起点出发的sg[]的异或和
如果为0,输,反之赢
#include<bits/stdc++.h>
#define MAXN 1005
using namespace std;
int to[MAXN<<1],nxt[MAXN<<1],head[MAXN<<1];
int tot=0;
int sg[MAXN<<1];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
memset(sg,-1,sizeof(sg));
}
void add(int u,int v)
{
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
int getsg(int x)
{
if(sg[x]!=-1) return sg[x];//非常重要的剪枝
int vis[MAXN];
memset(vis,0,sizeof(vis));
for(int i=head[x];~i;i=nxt[i])
{
int v=to[i];
sg[v]=getsg(v);
vis[sg[v]]=1;
}
for(int i=0;;i++)
if(vis[i]==0)
return i;
}
int main()
{
int n,m,x,q;
while(scanf("%d",&n)!=EOF)
{
init();
for(int i=0;i<n;i++)
{
scanf("%d",&m);
for(int j=1;j<=m;j++)
scanf("%d",&x),add(i,x);
}
while(scanf("%d",&q)&&q)
{
int ans=0;
while(q--)
{
scanf("%d",&x);
ans^=getsg(x);
}
printf("%s\n",ans==0?"LOSE":"WIN");
}
}
return 0;
}
https://www.luogu.org/problem/P2197
解析:
// 一种通俗易懂的方法证明nim游戏
// 如果堆数为1,那么傻子都能知道先手一定会赢......
// 如果堆数为2,那么相信大家都能自己yy出做法
// 如果你是先手,那么,如果两堆石子的个数不一样,恭喜,你赢了
// 你只要先把其中石子多的取到和石子少的一样,那么后面,对手在一堆中取多少个,你就在另一堆中取相同的个数,这很easy吧
// 当然,两堆石子如果个数一样,那么先手就输了。
// 现在,给你的石子有多少堆不知道,这应该怎么做呢?
// 我们又回到两堆石子的本质
// 其实 ,对于两堆石子来说,先手的赢和输,是依据这两堆石子的个数是否相等来判断的。
// 更深层次来说的话,两堆石子相同,即个数一样,那么,这不就是异或为0吗???
// 异或为0的表面是两个数相等,而两个数相等的实质是异或为0
// 这就需要我们透过表面看到实质的能力了 ^(* ̄(oo) ̄)^
// 所以,nim游戏,只要判断一下是不是所有的堆数异或结果为0即可
#include <bits/stdc++.h>
using namespace std;
bool jc;
int n,m,t,i,j,ans,x;
int main(){
scanf("%d",&t);
for (i=1; i<=t; i++)
{
scanf("%d",&n);
for (register int j=1; j<=n; ++j) scanf("%d",&x),ans=ans^x;
if (ans==0) cout<<"No"<<endl;
else cout<<"Yes"<<endl;
}
return 0;
}