ZJOI 2009 取石子游戏 博弈论

来源:互联网 发布:2016西决g6知乎 编辑:程序博客网 时间:2024/05/17 07:41

题目:

在研究过Nim游戏及各种变种之后,Orez又发现了一种全新的取石子游戏,这个游戏是这样的: 有n堆石子,将这n堆石子摆成一排。游戏由两个人进行,两人轮流操作,每次操作者都可以从最左或最右的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了。 Orez问:对于任意给出一个初始一个局面,是否存在先手必胜策略。


思路:

看到题毫无思路,难以找到获胜策略,看了一个大牛的题解才慢慢理解。确实是好题,又一次感受到博弈论的博大精深。

首先,设在区间[i, j]左边放l[i][j](可以为0)个石子后(区间内石子保持原状),产生的局面为先手必败局面,r[i][j]就是在右边放;设A[i]为第i堆石子个数。

接下来证明l[i][j]与(r[i][j]同理)是唯一的:

1)若存在两或以上个l[i][j],则从某一个必定可一步转移到另一个,矛盾,故最多只有一个;

2)若没有l[i][j],那么必胜态只可能通过拿右边的石子转移到某个必败态,由于此时左边石子可以是任意个,右边石子数固定,于是必有右边的某一个石子数对应了多种必败态,这些必败态只有左边的石子数不同,与1矛盾。

最后我们考虑l[i][j]的求法,设L = l[i][j-1], R = r[i][j-1], x = A[j],大力分类讨论:

1)边界条件:l[i][i] = r[i][i] = A[i],两堆一样的,后手照着先手拿的数量在另一堆拿即可;

2)若 x = R, 则[i, j]本身就是先手必败态,l[i][j] = 0;

3)若 x  < L && x < R, l[i][j] = x, 因为此时当两堆石子相同时,后手照着先手取,这样先手一定会先拿完某一堆,后手占据主动,这之后又会出现某一种我们分类讨论中的情况,相当于一种递归,直到后手获胜。

4)若 x < L && x > R, l[i][j] = x - 1。此时如果先手在左边拿使其个数变为y,若y < R,则后手在右边也拿到y即可变为情况3;若y >= R,则后手在右边拿到y + 1, 于是回到了相同的情况。如果先手在右边拿到y,若y < R,同样可到情况3;若y = R, 直接把左边的堆拿完,先手便面临败局;若y > R,则在左边拿到y - 1,回到相同情况。

5)若 x < R && x >= L, l[i][j] = x + 1, 与情况4恰好相反。

6)若 x > L && x > R,l[i][j] = x。若先手将某一堆拿到了y,且y > R, 跟着先手拿即可回到相同情况;若y < L,回到情况3;若在在左边拿到L或在右边拿到R,后手拿掉另一堆即可;否则根据情况在另一堆拿到y - 1或是y + 1,分别对应情况4和5。

r[i][j]的求法与l[i][j]完全对称,最后只需判断 A[1] == l[2][n]。

代码:

#include <cstdio>#include <iostream>#define For(i,j,k) for(int i = j;i <= k;i++)const int N = 1010;int A[N], l[N][N], r[N][N], n;int main(){    int T;    scanf("%d", &T);    while(T--){        scanf("%d", &n);        For(i,1,n) scanf("%d", &A[i]), l[i][i] = r[i][i] = A[i];        For(i,1,n-1)            For(u,1,n-i){                int v = u + i, L, R, x;                L = l[u][v-1], R = r[u][v-1], x = A[v];                if(x == R) l[u][v] = 0;                else if((x < L && x < R) || (x > L && x > R)) l[u][v] = x;                else if(x < R && x >= L) l[u][v] = x + 1;                else l[u][v] = x - 1;                L = l[u+1][v], R = r[u+1][v], x = A[u];                if(x == L) r[u][v] = 0;                else if((x < L && x < R) || (x > L && x > R)) r[u][v] = x;                else if(x < L && x >= R) r[u][v] = x + 1;                else r[u][v] = x - 1;            }        printf("%d\n", A[1] != l[2][n]);    }    return 0;}



2 0
原创粉丝点击