【博弈】(一)拿硬币,巴什博奕,一些简单的规律

综述

博弈类问题有一种就是找到规律特别简单,找不到根本做不出来。前一段时间出去比赛被博弈给教育了,所以出个专题研究一下。

1.最经典的拿硬币游戏

给定k个数字,a1~ak。共有x枚硬币。Alice和Bob轮流取硬币,一次只能取数列a中某项的枚数。A先取。取走最后一枚硬币获胜。双方都采取最优策略(废话),谁会赢。(假定a[]中一定有1)。

这个题目是一个非常有趣的递归。
由必败状态递归:
【博弈】(一)拿硬币,巴什博奕,一些简单的规律_第1张图片

给出一段DP代码:

#include 
using namespace std;

int x,k,a[100];
bool win[10001];

int main()
{
    cin >> x >> k ;
    for(int i=0;icin >> a[i];

    win[0] = false;

    for(int j=1;j<=x;j++){//从后往前递推
        //let the rival lose , then win
        win[j] = false;
        for(int i=0;i//最优策略
        }
    }

    if(win[x])cout << "Alice";
    else cout << "Bob";
    return 0;
}

2.巴什博奕(hdu1846)

Alice 和 Bob 都看上了一张藏宝图,卖家决定让两人竞价。
输入n和m(0< n,m <= 1e9)表示最高价和每次加价的上限(1~m)。Alice先出价,最先加到最高价者获胜。

这道题的数据规模显然是不可能递归了,但是加价是从1~m的。一定有规律。
规律如下:

若(m+1) | n,则先手必败,否则先手必胜。
如果n=m+1,因为最多取m个,所以先拿的人拿多少个,后拿的人能全拿走。
由此可以总结出 取胜秘籍: n=(m+1)*r+s (r为任意自然数,s为小于等于m)。
那么先取者拿走s个物品,如果后取者拿走K(≤m)个,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。
于是,这道题解法就出来了,只要判断 n%(m+1)是否等于等于0。
等于0代表,无论怎么取,最后都会剩下<=m个,因此后取者将会获胜。

就这么神奇:

#include 

using namespace std;

int main()
{
    int n,m;
    cin >> n >> m;

    if(n%(m+1))cout << "Alice";
    else cout << "Bob";

    return 0;
}

这个规律其实对初学者来讲不太好立即,赛场上可以举例子YY。
更多bashgame的题目以后再放。

3.翻翻棋(fzu 2230)

题目
象棋翻翻棋(暗棋)中双方在4*8的格子中交战,有时候最后会只剩下帅和将。根据暗棋的规则,棋子只能上下左右移动,且相同的级别下,主动移动到地方棋子方将吃掉对方的棋子。将和帅为同一级别。然而胜负在只剩下帅和将的时候已定。
Input
第一行T,表示T组数据。
每组数据共有四行字符串,每行字符串共八个字符
’#’表示空格
’*’表示红方帅
’.’表示黑方将
此时红方先走
每组输入之间没有空行。
Output
每组数据输出一行。若为红方赢输出Red win,否则输出 Black win
Sample Input
1
######.#
#####*##
########
########
Sample Output
Black win

这盘棋只有两个人走,所以其实两人位置一定,胜负已成定数。(这种洞见,是博弈最有趣的地方)
所以,只要分析两者之间相差步数。如果差奇数个,那么先手是追,后手是逃,最后堵在角落。如果偶数,线手逃。

#include 
#include 

using namespace std;

char map[4][8];

int fab(int a)
{
    return a>0?a:(-a);
}

pair<int,int> B,R;

int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        for(int i=0;i<4;i++){
            for(int j=0;j<8;j++){
                //scanf("%c",&map[i][j]);
                cin >> map[i][j];
                if(map[i][j]=='.'){B.first=i+1;B.second=j+1;}
                if(map[i][j]=='*'){R.first=i+1;R.second=j+1;}

        }
        int dis=(fab(B.first-R.first)+fab(B.second-R.second));
        //printf("%d",dis);
        //printf("%d %d %d %d",B.first,B.second,R.first,R.second);
        if(dis%2==0)printf("Black win\n");
        else printf("Red win\n");
    }
    return 0;
}

4.一圈硬币连续取游戏(POJ 2484)

传送门
围成圈的硬币,每次只能取一个或连续的两个(取完断开就不连续了)。
数据规模非常大,找找巧妙的判断方法。
其实很简单,就是A取完硬币,B根据奇偶性取走1~2枚硬币,可以得到两条枚数一样的链。这样A再取什么,B只要在另一边照做就行了。这是个先手必败的游戏……哦,除非Alice上来就能取光所有硬币。

using namespace std;  

int main()  
{  
    int n;  
    while(scanf("%d", &n))  
    {  
        if(n==0)  
            break;  
        if(n <= 2)  
            printf("Alice\n");  
        else  
            printf("Bob\n");  
    }  
    return 0;  
}  

5.辗转相除游戏(poj 2348)

输入俩个整数a,b,Stan和Ollie轮流从较大的数字中减去较小数字的倍数。(正整数倍)相减后结果不能小于零。屎蛋先手,先搞到零的一方获胜。

稍微有点难度。

假设一个这样的状态T1=(x,y),并且x>y,
那么状态T2=(x+y,y)可以到达T1,并且两者之间一个为必胜状态,那么另外一个为必败状态
但是,T3=(x+2y,y)既可以到达T1状态,又可以到达T2状态,那么我们可以说T3状态是一个必胜状态,
同理T4=(x+3y,y),…(x+4y,y)等几个状态,也同样可以到达T1与T2状态,那么这些状态也为必胜状态
所以我们说如果满足x>=2y,则为必胜状态
如果是y< x<2y,这种情况,下个状态只能为(x-y,y),为了保证前面一个数大于后面一个数,我们会将两者交换(确保x>y)。

所以当,(4,17)这种状态是必胜态(N),只需要循环找到x-y < y的状态就好咯。
刚刚看到有人管这类题叫做推理题,感觉挺形象的。

#include 

using namespace std;

int main()
{
    int a,b;
    cin >> a >> b;
    bool f = true;
    for(;;){
        if(a > b)swap(a,b);
        if(b%a == 0)break;//必胜
        if(b-a > a)break;//必胜

        b -= a;
        f = !f;
    }

    if(f) cout << "Stan wins";
    else cout << "Ollie wins";
    return 0;
}

你可能感兴趣的:(#ACM刷题笔记,入门算法&数据结构)