关于博弈论的博客:点击打开链接
看几遍博客再做题是极好的。
下面题的连接:http://acm.hust.edu.cn/vjudge/contest/126694 密码:swustacm
hdu1846
巴什博奕,看博客
#include
using namespace std;
int main(void)
{
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
if(n%(m+1) == 0) puts("second");
else puts("first");
}
return 0;
}
hdu 2147
题意:给一个n*m的棋盘,右上角(1,m)有一个棋子,每次可以向左,向下,向左下的格子移动,谁不能动谁就输。就是(1,m)走到(n,1),
开始以为可以走任意多步,还以为是威佐夫博弈,结果只能走一步。这种情况根据最后的必败态往前面推就行了。
P:必败态。
N:必胜态。
找出规律,P点的坐标都是奇数。
#include
using namespace std;
int main()
{
int n,m;
while(scanf("%d%d",&n,&m) && (n||m)){
if(n%2 && m%2) printf("What a pity!\n");
else printf("Wonderful!\n");
}
return 0;
}
hdu 2188
又是巴什博奕,题意要求是由n开始降,每次最多降m,最先降到0为胜,和从0开始一直升,最先升到n为胜是一个模型。
#include
using namespace std;
int main()
{
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
if(n%(m+1)) puts("Grass");
else puts("Rabbit");
}
return 0;
}
巴什博奕求第一次取的方案。
排除了输的情况(n%(m+1) == 0 )之后。输出方案,当m大于n的时候直接输出n--m就行了,m
#include
using namespace std;
int main()
{
int n,m;
while(scanf("%d%d",&n,&m) != EOF){
if(n%(m+1) == 0){
puts("none");
continue;
}
if(m >= n){
for(int i = n;i < m;i++)
printf("%d ",i);
printf("%d\n",m);
}
else{
printf("%d\n",n%(m+1));
}
}
return 0;
}
又是巴什博奕,我不会推,(这种不都是打表找规律吗
人家推的:点击打开链接
#include
using namespace std;
void Init()
{
int cf[10];
cf[0] = 1;
for(int i = 1;i < 8;i++) cf[i] = cf[i-1]*2;
int a[1000] = {0};
a[1] = a[2] = 1;
for(int i = 1;i <= 100;i++){
if(a[i] == 0){
for(int j = 0;j < 8;j++)
a[i+cf[j]] = 1;
}
}
for(int i = 1;i <= 30;i++)
printf("%d %d\n",i,a[i]);
}
int main()
{
//Init();
int n;
while(scanf("%d",&n) != EOF){
if(n%3 == 0) puts("Cici");
else puts("Kiki");
}
return 0;
}
最简单的尼姆博弈,棋子的位置相当于石子的个数,移动就相当于取石子。
#include
using namespace std;
int main()
{
int n;
while(scanf("%d",&n) && n){
int ans = 0;
for(int i = 1;i <= n;i++){
int t;
scanf("%d",&t);
ans ^= t;
}
if(ans == 0) puts("Grass Win!");
else puts("Rabbit Win!");
}
return 0;
}
题目是求尼姆博弈中先手能赢的前提下第一步能操作的方案数。
As we know,假如(a,b,c),其中一种方案是在c中取c-(a^b)(c>=(a^b))个(证明就看那个博客吧),那么针对每一种情况是不是只要a[i]>=a[1]^a[2]....a[i-1]^a[i+1]^a[n]就是一种可行方案了。可以先把所有a[i]异或起来可以降低复杂度。
#include
using namespace std;
int main()
{
int n;
int a[110];
while(scanf("%d",&n) && n){
int tmp = 0;
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
tmp ^= a[i];
}
if(tmp == 0){
printf("0\n");
continue;
}
int ans = 0;
for(int i = 1;i <= n;i++){
tmp ^= a[i];
if(a[i] >= tmp) ans++;
tmp ^= a[i];
}
printf("%d\n",ans);
}
return 0;
}
3堆石子,每次只能取fib个,问先手是否必胜。
sg函数的初等应用,只有1000,直接打表sg函数就行了,感觉这篇写的挺好的点击打开链接
#include
using namespace std;
int a[10000];
int f[30];
void Init() ///构造fib序列
{
f[1] = 1;f[2] = 2;
for(int i = 3;i < 30;i++)
{
f[i] = f[i-2]+f[i-1];
}
}
int sg[2000];
int sg_init(int x) ///记忆画搜索求sg函数
{
if(sg[x] != -1) return sg[x];
int vis[1005] = {0};
for(int i = 1;f[i] <= x;i++)
vis[sg_init(x-f[i])] = 1;
for(int i = 0;;i++)
if(!vis[i]) return sg[x] = i;
}
int main(void)
{
Init();
memset(sg,-1,sizeof sg);
sg[0]=0;
int n,m,p;
while(scanf("%d%d%d",&n,&m,&p) && (n||m||p)){
int ans = sg_init(n)^sg_init(m)^sg_init(p);
if(ans == 0) puts("Nacci");
else puts("Fibo");
}
return 0;
}
题意:两个人从1开始,轮流每个人在上一个人的基础上乘2-9的数,谁先超过n谁就赢。stan先手。
手动模拟能发现,1-9 stan胜,10-18 oll胜,19-162 stan胜,163-324 oll胜。
然后可以发现规律就是,轮流从1开始,乘9以内是stan胜,然后乘2是oll胜。然后就是手动模拟了。
别人写得更细一些:点击打开链接
#include
using namespace std;
int main()
{
int n;
while(scanf("%d",&n) != EOF){
int cnt = 0;
int t = 1;
int tmp = 0;
while(t < n){
if(tmp == 0) t *= 9;
else t *= 2;
tmp = 1-tmp;
cnt++;
}
if(cnt%2 == 1) puts("Stan wins.");
else puts("Ollie wins.");
}
return 0;
}
集合s里面有k个数字。有m个nim游戏,规则是每次只能取s集合里面的数字个数的石子。先手赢输W,否则输出L。
输入,是先输入一个k,再输入k个数字代表s集合。然后输入一个m,代表m次nim游戏,每组输入一个n,然后输入n堆石子。
采用sg记忆画搜索做,和fib那个石子游戏基本上一样。
#include
using namespace std;
const int N = 10000+10;
int k,n;
int SG[N];
int s[N];
int sg(int x)
{
if(SG[x] != -1) return SG[x];
int vis[100] = {0}; ///开100就够了
for(int i = 1;i <= k && s[i] <= x;i++){
vis[sg(x-s[i])] = 1;
}
for(int i = 0;;i++)
if(!vis[i]) return SG[x] = i;
}
int main()
{
while(scanf("%d",&k) && k){
memset(SG,-1,sizeof SG);
SG[0] = 0;
for(int i = 1;i <= k;i++) scanf("%d",&s[i]);
sort(s+1,s+1+k);
int m;
scanf("%d",&m);
while(m--){
scanf("%d",&n);
int ans = 0;
for(int i = 1;i <= n;i++){
int t;
scanf("%d",&t);
ans ^= sg(t);
}
if(ans) printf("W");
else printf("L");
}
puts("");
}
return 0;
}
后面做的,但是我不会添加题目
题意:有一个n*m的贴纸,每次可以横着减,可以竖着减,先剪出1*1矩阵的赢
同样sg记忆画搜索。
我们可以发现的是当某一种状态是(1,x)或者(x,1)的时候是必胜的(一定能剪出1,1)。
sg[1][1]标记为必败态(输入大于2)。然后对于每个(n,m)点,可以转移到横着每一行剪,竖着每一行剪。但是有个问题是一定不要转移到只有一行和只有一列的情况,所以搜索的时候,从第二行开始,只转移到第n-2行就行了(列同样)。然后就记忆化搜索了。
#include
#include
#include
#include
using namespace std;
const int N = 200+10;
int n,m;
int SG[N][N];
int sg(int n,int m)
{
if(SG[n][m] != -1) return SG[n][m];
int vis[400] = {0};
for(int i = 2;i < n-1;i++){
vis[sg(i,m)^sg(n-i,m)] = 1;
}
for(int i = 2;i < m-1;i++){
vis[sg(n,i)^sg(n,m-i)] = 1;
}
for(int i = 0;;i++)
if(!vis[i]) return SG[n][m] = i;
}
int main(void)
{
memset(SG,-1,sizeof SG);
SG[1][1] = 0;
while(scanf("%d%d",&n,&m) != EOF){
if(sg(n,m)) puts("WIN");
else puts("LOSE");
}
return 0;
}