专题训练之博弈

推荐几篇文章:https://blog.csdn.net/strangedbly/article/details/51137432(SG函数详细介绍)

https://blog.csdn.net/shahdza/article/details/7832997 博弈题集

SG函数解题模型:

1.把原游戏分解成多个独立的子游戏,则原游戏的SG函数值是它的所有子游戏的SG函数值的异或。

       即sg(G)=sg(G1)^sg(G2)^...^sg(Gn)。

2.分别考虑没一个子游戏,计算其SG值。

     SG值的计算方法:(重点

      A.可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);

   B.可选步数为任意步,SG(x) = x;

   C.可选步数为一系列不连续的数,用模板计算。

模板:假设起点为n,总共有m种移动步数可以选择

1.打表(首选打表预处理)

思路:对于一个位置,找到其所有的后继点的SG值有哪些,该位置的SG值即为不等于其后继点的SG值的最小非负整数

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=1005;
 6 const int maxm=105;
 7 int f[maxm],sg[maxn]; //f表示移动步数,sg表示每个位置的sg值 
 8 bool vis[maxn]; //vis表示对于某个位置来说,其后继节点的sg值有哪些 
 9 int n,m;
10 
11 void get_sg()
12 {
13     int i,j;
14     memset(sg,0,sizeof(sg));
15     for ( i=1;i<=n;i++ ) {
16         memset(vis,false,sizeof(vis));
17         for ( j=1;f[j]<=i&&j<=m;j++ ) vis[sg[i-f[j]]]=true; //对后继节点所处的位置标记为true 
18         for ( j=0;j<=i;j++ ) { //该位置的sg值 
19             if ( !vis[j] ) {
20                 sg[i]=j;
21                 break;
22             } 
23         }
24     }
25 }
打表求SG值

2.dfs

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=1005;
 6 const int maxm=105; 
 7 int f[maxn],sg[maxn],n,m;  //sg除了位置0初始化为0外,其余点初始化为-1 
 8 bool vis[maxn];
 9 
10 int dfs(int x)
11 {
12     if ( sg[x]!=-1 ) return sg[x];
13     memset(vis,false,sizeof(vis));
14     for ( int i=1;i<=m;i++ ) {
15         if ( x>f[i] ) {
16             int y=dfs(x-f[i]);
17             vis[y]=true;
18         }
19     }
20     for ( int i=0;i<=n;i++ ) {
21         if ( !vis[i] ) return sg[x]=i;
22     }
23 }
DFS求SG值

打表找规律求SG值:这类题目可以通过暴力求得小范围的SG值,再通过找规律得到SG值取值的规律

1.(HDOJ2897)http://acm.hdu.edu.cn/showproblem.php?pid=2897

分析:bash博弈变形,一般的bash博弈是从[1,m]个数中取一个,判断先手是否获胜只用看n%(1+m)是否为0。此题是从[p,q],当i<=p时都为必败点,当p<i<=q时都为必胜点。而原先必定是以(p+q)为周期的。所以只需对n%(p+q),若结果为0或者结果大于p则为输出WIN,否则输出LOST

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     int n,q,p;
 9     while ( scanf("%d%d%d",&n,&p,&q)!=EOF ) {
10         n%=(p+q);
11         if ( n>p || n==0 ) printf("WIN\n");
12         else printf("LOST\n");
13     }
14     return 0;
15 }
HDOJ2897

2.(HDOJ3032)http://acm.hdu.edu.cn/showproblem.php?pid=3032

题意:有n堆,每堆有s[i]个,每次可以从一堆中取走[1,a[i]]个或者将这堆分成任意数量的两堆,Alice先,求最后谁获胜

分析:暴力打表得sg+找规律。得到sg( 4k+1 ) = 4k+1; sg( 4k+2 ) = 4k+2; sg( 4k+3 ) = 4k+4; sg( 4k+4 ) = 4k+3;最后再将这n个sg值异或和即可。

规律的正确性证明:http://acm.hdu.edu.cn/discuss/problem/post/reply.php?postid=29685&messageid=1&deep=0

变形:可以分成任意数量的三堆

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 
 7 int main()
 8 {
 9     ll T,n,i,j,k,x,y,z,ans;
10     scanf("%lld",&T);
11     while ( T-- ) {
12         scanf("%lld",&n);
13         ans=0;
14         for ( i=1;i<=n;i++ ) {
15             scanf("%lld",&x);
16             if ( x%4==3 ) x++;
17             else if ( x%4==0 ) x--;
18             ans^=x;
19         }
20         if ( ans!=0 ) printf("Alice\n");
21         else printf("Bob\n");
22     }
23     return 0;
24 }
HDOJ3032

3.(HDOJ3537)http://acm.hdu.edu.cn/showproblem.php?pid=3537(翻硬币模型)

翻硬币游戏

一般的翻硬币游戏的规则是这样的:

枚硬币排成一排,有的正面朝上,有的反面朝上。我们从左开始对硬币按编号。

第一,游戏者根据某些约束翻硬币,但他所翻动的硬币中,最右边那个硬币的必须是从正面翻到反面。例如,只能翻3个硬币的情况,那么第三个硬币必须是从正面翻到反面。如果局面是正正反,那就不能翻硬币了,因为第三个是反的。

第二,谁不能翻谁输。有这样的结论:局面的SG 值为局面中每个正面朝上的棋子单一存在时的SG 值的异或和。即一个有k个硬币朝上,朝上硬币位置分布在的翻硬币游戏中,SG值是等于k个独立的开始时只有一个硬币朝上的翻硬币游戏的SG值异或和。比如THHTTH这个游戏中,2号、3号、6号位是朝上的,它等价于THTTHTTTTTH三个游戏和,即sg[THHTTH]=sg[TH]^sg[TTH]^sg[TTTTTH].我们的重点就可以放在单个硬币朝上时的SG值的求法。

推荐博客:http://www.cnblogs.com/kuangbin/p/3218060.html(翻硬币模型的8种变形)

总结:对于每种模型,先将其变成求单个位置上的sg值,如:T,FT,FFT,FFFT(F表示反面,T表示正面),再根据游戏规则求出该位置的后驱有几种情况,并且求出每种情况的SG值是多少,进而得到该位置的SG值。然后根据前几组数据找规律

注意:对于本题而言,对于某个位置i来说,当二进制表示下1的个数为1时sg[i]=2*i,否则为sg[i]=2*i+1。特别注意本题的输入数据可能包含同一个位置的硬币,所以需要用set记录已经使用过的位置

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<set>
 5 using namespace std;
 6 set<int>st;
 7 
 8 bool judge(int x)
 9 {
10     int cnt=0;
11     for ( int i=0;(1<<i)<=x;i++ ) {
12         int y=1<<i;
13         if ( y&x ) cnt++;
14     }
15     if ( cnt%2==1 ) return true;
16     else return false;
17 }
18 
19 int main()
20 {
21     int n,i,j,k,x,y,z,ans;
22     while ( scanf("%d",&n)!=EOF ) {
23         ans=0;
24         st.clear();
25         for ( i=0;i<n;i++ ) {
26             scanf("%d",&x);
27             if ( st.find(x)!=st.end() ) continue;
28             st.insert(x);
29             if ( judge(x) ) x=2*x;
30             else x=2*x+1;
31             ans^=x;
32         }
33         if ( ans ) printf("No\n");
34         else printf("Yes\n");
35     }
36     return 0;
37 }
HDOJ3537

反nim博弈:

1.(HDOJ2509)http://acm.hdu.edu.cn/showproblem.php?pid=2509

题意:有n堆,每堆有a[i]个,现在每次可以从第i堆中取[1,a[i]]个,谁取到最后一个谁输(nim博弈为谁取到最后一个谁赢)

分析:首先给出结论:先手胜当且仅当 ①所有堆石子数都为1且游戏的SG值为0(即有偶数个孤单堆-每堆只有1个石子数);②存在某堆石子数大于1且游戏的SG值不为0.

给出简略证明:对于①来说当每堆石子数都为1时最后的结果一定是固定的,所以易得当有偶数堆1时最后先手一定会输,反之先手一定会赢

对于②来说。先假设只有一堆(记作第X堆)的石子数个数大于1(此时游戏的SG值一定不为0),对于剩下的堆数则满足①,若剩下石子数为1的堆数为奇数,为确保先手获胜,那么先手直接取完第X堆中的所有石子。若剩下石子数为1的堆数为偶数,那么我们可以将第X堆的石子取到只剩1个,那么对于该游戏来说石子数为1的堆数变成了奇数。

当石子数大于1的堆数超过一堆时,此时若游戏的SG值不为0时,先手只需要将游戏的SG值变成0,剩下的事就大体同Nim博弈了,只是Nim博弈最后取走最后一堆的所以石子,而此题最后一堆时只需要留一颗石子即可

 1 #include<cstdio>
 2 using namespace std;
 3 
 4 int main()
 5 {
 6     int n,ans,i,x;
 7     bool flag;
 8     while ( scanf("%d",&n)!=EOF ) {
 9         ans=0;
10         flag=false;
11         for ( i=1;i<=n;i++ ) {
12             scanf("%d",&x);
13             ans^=x;
14             if ( x>1 ) flag=true;
15         }
16         if ( flag ) {
17             if ( ans==0 ) printf("No\n");
18             else printf("Yes\n");    
19         }
20         else {
21             if ( n%2 ) printf("No\n");
22             else printf("Yes\n");
23         }
24     }
25     return 0;
26 }
HDOJ2509

2.(HDOJ1907)http://acm.hdu.edu.cn/showproblem.php?pid=1907

同上

 1 #include<cstdio>
 2 using namespace std;
 3 
 4 int main()
 5 {
 6     int n,ans,i,x,T;
 7     bool flag;
 8     scanf("%d",&T);
 9     while ( T-- ) {
10         scanf("%d",&n);
11         ans=0;
12         flag=false;
13         for ( i=1;i<=n;i++ ) {
14             scanf("%d",&x);
15             ans^=x;
16             if ( x>1 ) flag=true;
17         }
18         if ( flag ) {
19             if ( ans==0 ) printf("Brother\n");
20             else printf("John\n");    
21         }
22         else {
23             if ( n%2 ) printf("Brother\n");
24             else printf("John\n");
25         }
26     }
27     return 0;
28 }
HDOJ1907

猜你喜欢

转载自www.cnblogs.com/HDUjackyan/p/8858774.html