①博弈模型为两个人轮流决定的非合作博弈,即两个人轮流进行决策,并且每次都会采用最优策略。
②博弈模型必须是有限次可以完成的。
③对两个人的规则是公平的。
P状态(必败态):前一个选手(Previous player)将取胜的位置称为必败点。
N状态(必胜态):下一个选手(Next player)将取胜的位置称为必胜点。
①将所有终止状态定义为P状态
②将所有可以一步到达P状态的点,定义为N状态
③如果存在某个状态,它的下一步都是N状态,定义为P状态
④循环②,③步骤
只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
判断 n % ( m + 1 ) n\%(m+1) n%(m+1),不为零时获胜。先手第一次取走 n % ( m + 1 ) n\%(m+1) n%(m+1)个物品,然后根据后手所取数目 k k k,再取走 ( m + 1 − k ) (m+1-k) (m+1−k)个即可。
hdu 1846。
http://acm.hdu.edu.cn/showproblem.php?pid=1846
#include
using namespace std;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
return num*f;
}
int main()
{
int c=read();
while (c--)
{
int n=read(),m=read();
if (n%(m+1))
puts("first");
else
puts("second");
}
return 0;
}
有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜
使用 ( a k , b k ) (ak,bk) (ak,bk)来表示两个堆里面的物品数,则P状态满足:
a k = k ∗ [ ( 1 + √ 5 ) / 2 ] , b k = a k + k , ( k = 0 , 1 , 2 , 3...... ) ak=k*[(1+√5)/2],bk=ak+k,(k=0,1,2,3......) ak=k∗[(1+√5)/2],bk=ak+k,(k=0,1,2,3......)
poj 1067
http://poj.org/problem?id=1067
//#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
double t=(1+sqrt(5.0))/2.0;
int a,b;
while (scanf("%d%d",&a,&b)==2)
{
if (a>b)
swap(a,b);
int k=b-a;
if (a==(int)(k*t))
puts("0");
else
puts("1");
}
return 0;
}
看着这么长的头文件,,,,我有点虚。。
简称Nim模型:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
使用 ( A 1 , A 2 , A 3 ) (A1,A2,A3) (A1,A2,A3)来描述所处状态 a n s = A 1 x o r A 2 x o r A 3 ans=A1 xor A2 xor A3 ans=A1xorA2xorA3如果ans为零则为P状态。
定理的证明自己网上找。
自己的理解:Nim模型,为多种情况相同处理的并,而且处理只能是取走任意颗石子
http://acm.hdu.edu.cn/showproblem.php?pid=1850
#include
using namespace std;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
return num*f;
}
//Nim组合博弈问题
const int maxn=1e6+10;
int m,n[maxn];
int main()
{
while (scanf("%d",&m) &&m)
{
int count=0,ans=0;
for (int i=1;i<=m;++i)
{
n[i]=read();
ans^=n[i];
}
/*这里有个技巧,首先求出所有的异或值,
如果想去除的话,只需要再次进行异或操作即可,
因为x^x=0,执行两次等于没有执行。
原来每次都需要进行一次计算,
这样可使算法效率从O(n*n)缩小到了O(n)*/
for (int i=1;i<=m;++i)
if ((ans^n[i])<n[i])
++count;
printf("%d\n",count);
}
return 0;
}
这里涉及到一些不同的东西。直接写结论:必败态中有一种情况需要单独判断,奇数个孤单堆(即奇数个堆石子数为1)为必败态。还有一点是,两人在后面取石子的方式会有所不同,需要注意,详细解释在 http://www.cnblogs.com/tanky_woo/archive/2010/08/20/1804464.html 里面有。
当超过1颗石子的堆数大于1的时候,按照Nim的方法走。
直到超过1颗石子的堆数等于1,这时将这堆石子全部取掉或剩1颗,
保证非空(剩下1颗石子)的堆数为奇数。
http://acm.hdu.edu.cn/showproblem.php?pid=1907
//这个问题关键在最后取走的为失败者。
#include
using namespace std;
const int maxn=4747+10;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
return num*f;
}
int a[maxn];
int main()
{
int t=read();
while (t--)
{
int n=read(),ans=0,sum=0;
for (int i=1;i<=n;++i)
{
a[i]=read();
ans^=a[i];
sum+=a[i];
}
if (sum==n)
printf("%s\n",sum%2?"Brother":"John");
else
printf("%s\n",ans?"John":"Brother");
}
return 0;
}
https://www.luogu.org/problemnew/show/P2197
#include
using namespace std;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
return num*f;
}
int main()
{
int t=read();
while (t--)
{
int n=read(),ans=0;
for (int i=1;i<=n;++i)
{
int num=read();
ans^=num;
}
if (!ans)
puts("No");
else
puts("Yes");
}
return 0;
}
有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗,最后取光着得胜。
首先来了解一下什么为SG函数,对于一个递增有界的图G(X,F)来说,SG函数g,是定义在X上的函数,函数值是非负整数,使得g(x)的值等于所有x的后继的SG函数中没有出现的最小非负整数。对于递增有界的图,SG函数是唯一的、有界的。所有的终止状态x,因为F(x)是空集,所以g(x)=0.
根据定义,考虑以下三点:
1. 如果x是终止状态,那么g(x)=0。
2. 一个状态x,如果g(x)≠0,那么一定存在一个x的后继y,使得g(y)=0。
3. 3.一个状态x,如果g(x)=0,那么所有x的后继y,都有g(y)≠0。
这三句话表明,顶点x所代表的position是P-position当且仅当g(x)==0.
计算出所有情况的SG值,最后求异或 g ( G ) = g ( G 1 ) x o r g ( G 2 ) x o r g ( G 3 ) . . . . . . . . g(G)=g(G1) xor g(G2) xor g(G3)........ g(G)=g(G1)xorg(G2)xorg(G3)........当 g ( G ) = = 0 g(G)==0 g(G)==0时为P状态。
定理2:
设 G = G 1 + G 2 + … + G n , G i G=G1+G2+…+Gn,Gi G=G1+G2+…+Gn,Gi的 S G 函 数 SG函数 SG函数是 g i , i = 1 , 2 , … , n gi,i=1, 2, …, n gi,i=1,2,…,n那么G的 S G 函 数 SG函数 SG函数为 g ( x 1 , x 2 , … , x n ) = g 1 ( x 1 ) + g 2 ( x 2 ) + … + g n ( x n ) g(x1, x2, …, xn)=g1(x1)+g2(x2)+…+gn(xn) g(x1,x2,…,xn)=g1(x1)+g2(x2)+…+gn(xn)加法表示Nim和,即不进位的二进制加法。
自己的理解:SG模型,为多种情况不同处理的并,每次都需要求一次g函数,处理可以随便进行
http://acm.hdu.edu.cn/showproblem.php?pid=1847
//第一种解法,历编自己找到规律
#include
using namespace std;
int main()
{
int n;
while (scanf("%d",&n)!=EOF)
{
if (n%3==0)
printf("Cici\n");
else
printf("Kiki\n");
}
return 0;
}
//第二种解法
#include
const int maxn=1e3+10;
int a[10],g[maxn];
int dfs(int n)
{
if (g[n]!=-1) return g[n];
bool h[15];//从①处可得知数组的大小
memset(h,0,sizeof(h));
int i;
for (i=0;i<10 && n-a[i]>=0;++i)
h[dfs(n-a[i])]=1;//①
for (i=0;h[i];++i);
return g[n]=i;
}
int main()
{
int n;
for (int i=0;i<10;i++)
a[i]=1<<i;
memset(g,-1,sizeof(g));
g[0]=0;
while (scanf("%d",&n)==1)
{
if (dfs(n))
puts("Kiki");
else
puts("Cici");
}
return 0;
}
http://acm.hdu.edu.cn/showproblem.php?pid=1536
#include
using namespace std;
const int maxm=105,maxn=10005;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
return num*f;
}
int k,n;
int s[maxm],H[maxm],g[maxn];
bool h[maxn][maxm];//从②处可得知,二维数组的大小。
int dfs(int n)
{
if (g[n]!=-1)
return g[n];
for (int i=1;i<=k;++i)
if (s[i]<=n)//①因为S并不一定是从小到大排序好的,所以只能用if语句进行判断。
// 纠错时,不要去找语法,语法不会有错,要去发现算法,哪一步出现了问题。或许是某个细节没有注意。
h[n][dfs(n-s[i])]=1;//②
int i;
for (i=0;h[n][i];++i);
return g[n]=i;
}
int main()
{
while (1)
{
k=read();
if (!k) break;
for (int i=1;i<=k;++i)
s[i]=read();
memset(g,-1,sizeof(g));
memset(h,0,sizeof(h));
g[0]=0;
int m=read();
while (m--)
{
int ans=0;
n=read();
for (int i=1;i<=n;++i)
{
scanf("%d",&H[i]);
ans^=dfs(H[i]);
}
if (ans)
printf("W");
else
printf("L");
}
printf("\n");
}
return 0;
}
http://acm.hdu.edu.cn/showproblem.php?pid=1848
#include
using namespace std;
const int maxn=17;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
return num*f;
}
int f[maxn],g[1005],num;
int fun(int n)
{
if (g[n]!=-1)
return g[n];
bool h[maxn];
memset(h,0,sizeof(h));
int i;
for (i=1;i<=num && f[i]<=n;++i)
h[fun(n-f[i])]=1;
for (i=0;h[i];++i);
return g[n]=i;
}
int main()
{
int i;
f[1]=1,f[2]=2;
for (i=3;f[i-1]<1000;++i)
f[i]=f[i-1]+f[i-2];
num=i-1;
memset(g,-1,sizeof(g));
while (1)
{
int m=read(),n=read(),p=read();
if (!m && !n && !p) break;
int ans=fun(m)^fun(n)^fun(p);
printf("%s\n",ans==0?"Nacci":"Fibo");
}
return 0;
}
n个阶梯,阶梯上有一些石子,要求将阶梯上的石子都放到地上,操作只能是从第i个阶梯取若干个石子到第i-1上
对只需要对奇数号码的阶梯进行Nim运算即可。
先取者使状态到P状态,如果后面一个人也从奇数号码阶梯取石子,则按照Nim步骤操作即可,从奇数阶梯到偶数阶梯相当于取出石子扔掉。
若从偶数号码阶梯取石子,则先取者只需要将相同的石子从个该阶梯传到下面一个即可。
这里有详细介绍:http://blog.csdn.net/kk303/article/details/6692506
poj 1704
http://poj.org/problem?id=1704
//#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1010;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
return num*f;
}
int p[maxn];
int cmp(const void *a ,const void *b)
{
return *(int *)a-*(int *)b;//从小到大排序,把a,b位置反过来就是从大到小
}
int main()
{
int t=read();
while (t--)
{
int n=read();
p[0]=0;
for (int i=1;i<=n;++i)
p[i]=read();
qsort(p,n+1,sizeof(p[0]),cmp);//编译器函数库自带的快速排序函数
int ans=0;
for (int i=n;i>=1;i-=2)
ans^=p[i]-p[i-1]-1;
if (!ans)
puts("Bob will win");
else
puts("Georgia will win");
}
return 0;
}
hdu 1517,寻找失败类自己找出规律来
http://acm.hdu.edu.cn/showproblem.php?pid=1517
/*
思路:寻找失败类
这道题是自己做出来的好兴奋啊,原来WA,
通过测试特殊数据找出了程序细节处理上的错误
*/
#include
using namespace std;
typedef long long ll;
int op(ll n)
{
if (n%18==0)
return n/18;
else
return n/18+1;
}
int main()
{
ll n;//使用double类型也可通过
while (scanf("%lld",&n)==1)
{
/*if (n==1)
{
puts("Stan wins.");
continue;
}
while (n>18) n=op(n);
if (n>1 && n<=9)
puts("Stan wins.");
else
puts("Ollie wins.");*/
//或者
while (n>18) n=op(n);//因为这里在n=18时候会停止循环,边界处理没有明确,1<=n<=18;
if (n<=9)
puts("Stan wins.");
else
puts("Ollie wins.");
}
return 0;
}
http://www.cnblogs.com/Knuth/archive/2009/09/05/1561005.html 寻找必败态——一类博弈问题的快速解法
这其实也是每类博弈题最先考虑的问题,或者突破点所在。
http://www.cnblogs.com/Knuth/archive/2009/09/05/1561002.html,Game theory初步,对两个定理有详细的证明
http://www.cnblogs.com/Knuth/archive/2009/09/05/1561008.html Nim游戏
http://www.cnblogs.com/Knuth/archive/2009/09/05/1561007.html Sprague-Grundy函数
http://www.cnblogs.com/tanky_woo/archive/2010/08/20/1804464.html 组合博弈知识汇总,这个博客里面有很多的补充
这篇博客大多出自刘子祯学长的《博弈论小结》,在此特向刘子祯大佬表示无比的敬意。%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%