转载自:http://blog.csdn.net/acmlzq/article/details/51212297
nyoj的取石子有好多道,除了两道难度为6的,剩下的在这儿简单总结一下结论。
有一堆石子共有n个,A和B轮流取,A先,每次最少取1个,最多取m个,先取完者胜,A,B足够聪明,问谁先胜?
比较简单的巴什博弈,若n%(m+1)!=0,A胜,否则B胜。
n个石子摆成一圈,A和B轮流取,每次可以从中取一个或相邻两个,先取完者胜,A先取,问谁胜?
若n==1||n==2 则A胜,否则B胜。
两堆石子分别n,m(n>=m)个,A和B轮流取,有两种取法,一是在任意的一堆中取走任意多的石子,最少为一;二是在两堆中同时取走相同数量的石子。A先取,先取完者胜,问A是否胜?(胜输出1,负为0)
著名的威佐夫博奕,题解链接:威佐夫博弈,
结论:若(n-m)*(sqrt(5.0)+1.0)/2.0!=m ,则A胜,否则负。
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=161
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
double solve(int n)
{
double m=(double)n;
return (sqrt(5)+1)/2*m;
}
int main()
{
int a,b;
while(~scanf("%d%d",&a,&b))
{
if(a>b)
swap(a,b);
if((int)solve(b-a)==a)
{
printf("0\n");
}
else
{
printf("1\n");
}
}
return 0;
}
题意同上取石子(四),不过现在要求前n(n<=10W)个必败态,比如A面对(0,0)必败,面对(1,2)(3,5)(4,7)也必败,现在给一个n,求前n个必败态。
由上面取石子(四)的题解链接可知其第n项为(n*(sqrt(5.0)+1.0)/2,n*(sqrt(5.0)+1.0)/2+n),由此打一个10W的表即可。
http://acm.nyist.net/JudgeOnline/problem.php?pid=837
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn =100000+100;
double solve(int n)
{
double m=(double)n;
return (sqrt(5)+1)/2*m;
}
int a[maxn][2];
int main()
{
for(int i=0;i<maxn;i++)
{
a[i][0]=(int)solve(i);
a[i][1]=a[i][0]+i;
}
int n;
while(~scanf("%d",&n))
{
for(int i=0;i<n;i++)
{
printf("(%d,%d)",a[i][0],a[i][1]);
}
printf("(%d,%d)\n",a[n][0],a[n][1]);
}
return 0;
}
题意同上上取石子(四),依然为威佐夫博弈,但是如果A胜,要你输出你第一次怎么取子,(如果在任意的一堆中取走石子能胜同时在两堆中同时取走相同数量的石子也能胜,先输出取走相同数量的石子的情况,假如取一堆的有多种情况,先输出从石子多的一堆中取的情况,且要求输出结果保证第二个值不小于第一个值。)
首先判断若n==0,则直接输出(0,0);
else for循环从头到尾把上面Wyothoff Game的第n项公式循环一遍,并分别与n,m比较。
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=886
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
int main()
{
int a,b,t;
double p=(1.0+(sqrt(5.0)))/2;//威佐夫博奕公式
while(scanf("%d%d",&a,&b)&&a!=0||b!=0)
{
if(a>b)
swap(a,b);
int s=b-a;
int k=floor(s*p);
if(k==a)//判断是否是必胜局面
printf("0\n");
else
{
printf("1\n");
if(a-k==b-(k+s)&&a>=k)//拿走相同数量的石子 注意两个条件都要写,不然WA
printf("%d %d\n",k,k+s);
for(int i=0; ;i++)
{
int temp=i*p;
if(temp>=b)
break;
if(b>temp+i&&a==temp)//从b中拿石子,且拿完b比a大
printf("%d %d\n",temp,temp+i);
else if(a==temp+i)//从b中拿石子,但拿完b比a小
printf("%d %d\n",temp,temp+i);
else if(b==temp+i&&a>temp)//从a中拿,拿完肯定a还是小于b
printf("%d %d\n",temp,temp+i);
}
}
}
return 0;
}
有n堆石子,每堆石子都有任意个,A和B轮流从取任意堆里取一定的石子,每次只能从一堆里至少取一个,A先取,先取完者胜,问谁胜?
此题为尼姆博弈,题解链接:尼姆博弈;
结论:将n个数异或一遍,若不为0,则A胜,否则B胜。
#include<stdio.h>
int main()
{
int s,n;
scanf("%d",&s);
while(s--)
{
scanf("%d",&n);
int sum=0;
int x;
for(int i=0;i<n;i++)
{
scanf("%d",&x);
sum^=x;
}
if(sum!=0)
printf("PIAOYI\n");
else
printf("HRDV\n");
}
return 0;
}
题意同取石子(六),不过先取完者败,问谁胜?
在尼姆博奕中取完最后一颗石子的人为赢家,而取到最后一颗石子为输家的就是反尼姆博奕。这道题就反尼姆
博奕的模型。在尼姆博奕中判断必胜局面的条件是所有堆石子数目相异或不等于0 。 而在反尼姆博奕中判断必胜局
面的条件有两点,满足任意一点先手都能取胜,即必胜局面。
1:各堆石子数目异或结果不等于0,且存在有石子数目大于1的石子堆。
2:各堆石子数目异或结果等于0,且所有石子堆数目全部为1。
代码:
#include<cstdio>
int main()
{
int ans,n,m,cnt,num;
scanf("%d",&n);
while(n--)
{
scanf("%d",&m);
ans=0;num=0;
while(m--)
{
scanf("%d",&cnt);
ans^=cnt;
if(cnt>1)
num++;
}
if((ans&&num)||(!ans&&!num))
printf("Yougth\n");
else
printf("Hrdv\n");
}
return 0;
}
题意同上上取石子(六),不过限定了每堆石子最多可以取的石子数(最少为一),问A是胜还是败?(第一行是一个整数T表示测试数据的组数(T<100)每组测试数据的第一行是一个整数N(1<N<100),表示共有N堆石子,随后的N行每行表示一堆石子,这N行中每行有两个数整数m,n表示该堆石子共有m个石子,该堆石子每次最多取n个。
(0<=m,n<=2^31))
尼姆博弈和巴什博奕的结合
题目思路:每一堆的必胜状态是 m%(n+1) 然后对每一堆的最优解进行异或,如果是非平衡状态则先手必胜,否则先手必败。
http://acm.nyist.net/JudgeOnline/problem.php?pid=135#include<stdio.h>
int main(){
int t;
scanf("%d",&t);
while(t--){
int N;
int sum=0;
scanf("%d",&N);
for(int i=1;i<=N;i++){
int n,m;
scanf("%d%d",&n,&m);
sum^=n%(m+1);
}
if(sum) puts("Win");
else puts("Lose");
}
return 0;
}
有一堆石子,A和B轮流从中取一定的石子,但规定:第一次不能取完,至少一个;从第二次开始,每个人取的石子数至少为1,至多为对手刚取的石子数的两倍。A先取,问A是否会胜?
具体过程:斐波那契博弈
结论:若其对应的石子数目刚好是斐波那契数,则A必败,否则A必胜。
详细解释:
-
结论:当n为fibonacci数时,先手必败(n >= 2);
-
证明:
-
以下为个人思路:
-
根据“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和(优先选取最大的)。
-
比如 n = 14 时, n = 8 + 5 + 1;而先手要赢的条件是,先手必须要先取完最小的一堆,再取完次小的一堆,以此类推,并保证每堆的最后一个是自己取到的而且不能取超!听起来有点绕,举个例子:
-
1、先看必胜态,n不是fibonacci数的时候,令n = 9 =5 + 3 + 1:
-
a、先手先取最小的一堆,1个,保证了最后一个是自己取得;
-
b、此时后手开始取次小的一堆(3个石子),上面说过,要想赢的话,要保证这一堆的第3个必须是自己取得;此时后手有 1个、2个两种取法,但此处无论后手怎么取,都能保证这一堆的最后一个(第3个)是自己取得;
-
c、此时后手开始取最后一堆(5个石子),b步先手可能取得是1或2,因此此步后手可能有1、2、3、4四种取法,易知后手取3或4的话,自己立马就能取到最后一个,就赢了,而取1或2时,同上(读者自己来),仍能保证取到最后一个(此堆第5个,全局最后一个),必赢!
-
2、再说必败态,若n本身是fibonacci数时,已经不需要分解了,为方便观察,令n=5:
-
实际上与上面相同,每一堆(有fib个石子)在自己先手的情况下,无论怎么取,后手都能保证自己取到这一堆的最后一个,因此后手必胜!
-
PS:所以上面说总石子数为非fibonacci数时先手必胜的原因在于,总数目分解为若干fib数时,先手先全部取走最小的一堆1个(也是取到了这一堆的最后一个),以后每堆(有fib[i]个石子)都是后手先取,先手便有把握取到这一堆的最后一个了!
#include <stdio.h>
#define N 100
long long fib[N];
void Fib()
{
fib[0] = 0;
fib[1] = 1;
for(int i = 2; i < N; i ++){
fib[i] = fib[i - 1] + fib[i - 2];
}
}
int main()
{
Fib();
long long n;
while(scanf("%lld", &n) != EOF){
bool is_fib = 0;
for(int i = 2; i < 100; i ++){
if(fib[i] == n){
is_fib = 1;
break;
}
}
if(is_fib)
printf("No\n");
else
printf("Yes\n");
}
return 0;
}