复杂博弈论
推荐的论文
硬币翻转博弈
SG函数
推荐的学习博客
谈谈我的理解:
SG函数非常的像多堆的Nim博弈,唯一的区别就是SG函数还可以向后面的状态跳,但是这是没有什么意义的,因为对手不论怎样都可以返回原来的亦或为0的状态。
求SG的两种姿势:
以递推的形式进行打表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <bits/stdc++.h> using namespace std; const int maxn = 1e3+10; int sg[maxn]; int f[maxn]; bool vis[maxn]; void solve(int n){ for(int i=1; i<=n; i++){ memset(vis, 0, sizeof(vis)); for(int j=0; f[j]<=i&&j<3; j++){ vis[sg[i-f[j]]] = true; } for(int j=0; j<=i; j++){ if(!vis[j]){ sg[i] = j; break; } } } for(int i=1; i<=n; i++){ cout<<i<<" "<<sg[i]<<endl; } } int main(){ f[0] = 1, f[1] = 2, f[2] = 4; solve(15); return 0; }
|
以dfs来进行打表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <bits/stdc++.h> using namespace std; const int maxn = 1e3+10; int sg[maxn]; int n, m; int s[maxn]; int dfs(int x){ if(sg[x]!=-1) return sg[x]; bool vis[maxn]; memset(vis, 0, sizeof(vis)); for(int i=0; i<m; i++){ if(x>=s[i]){ dfs(x-s[i]); vis[sg[x-s[i]]] = true; } } int ans = -1; for(int i=0; i<=x; i++){ if(!vis[i]){ ans = i; break; } } return sg[x] = ans; } int main(){ s[0] = 1, s[1] = 2, s[2] = 4; memset(sg, -1, sizeof(sg)); m = 3; n = 10; for(int i=1; i<=n; i++){ dfs(i); } for(int i=1; i<=n; i++){ cout<<i<<" "<<sg[i]<<endl; } return 0; }
|
dfs打表
骑士有6种移动的方式,注意状态空间的转移
AC code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <bits/stdc++.h> using namespace std; const int maxn = 1010; int sg[maxn][maxn]; int n, t; int dx[6] = {-1, 1, -1, -2, -2, -3}; int dy[6] = {-3, -2, -2, -1, 1, -1}; bool judge(int x, int y){ if(x<0) return false; if(y<0) return false; return true; } int dfs(int x, int y){ if(sg[x][y]!=-1) return sg[x][y]; bool vis[maxn]; memset(vis, 0, sizeof(vis)); for(int i=0; i<6; i++){ int nx = x+dx[i]; int ny = y+dy[i]; if(judge(nx, ny)){ vis[dfs(nx, ny)] = true; } } for(int i=0; i<maxn; i++){ if(!vis[i]){ return sg[x][y] = i; } } } int main(){ scanf("%d", &t); int kase = 1; memset(sg, -1, sizeof(sg)); while(t--){ scanf("%d", &n); int x, y; int ans = 0; for(int i=0; i<n; i++){ scanf("%d%d", &x, &y); ans^=dfs(x, y); } if(ans){ printf("Case %d: Alice\n", kase++); } else{ printf("Case %d: Bob\n", kase++); } } return 0; }
|
取石子游戏
游戏的规则是每一次只能取一堆石子个数的一半,向下取整。
不能取的人失败
先对单堆的石子求SG函数,发现以下的规律:
- 当n是偶数的时候, SG(n) = n/2;
- 当n是奇数的时候, SG(n) = SG(n/2);这里的是向下取整
递归的计算奇数堆的SG值就可以了
AC code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include <bits/stdc++.h> using namespace std; typedef long long ll; ll dfs(int x){ if(x%2 == 0) return x/2; else return dfs(x/2); } int main(){ ios::sync_with_stdio(false); int t; scanf("%d", &t); int kase = 1; while(t--){ int n; scanf("%d", &n); int ans = 0; int temp; for(int i=0; i<n; i++){ scanf("%d", &temp); ans ^= dfs(temp); } if(ans){ printf("Case %d: Alice\n", kase++); } else{ printf("Case %d: Bob\n", kase++); } } return 0; }
|
E 取石子游戏2
知识的参考博客
有若干堆石子,但是最后取走石子的人为负,问先手必胜还是先手必败。
这是Nimm game的第二种形式
这里有关于充裕堆的定义:
定义:若一堆中仅有1根火柴,则被称为孤单堆。若大于1根,则称为充裕堆。
下面定义几种状态:
- T:石子的各堆的个数亦或和为0。(利他态)
- S:各堆的石子的个数亦或和不为0.(利己态)
1.1: \(T_2\): 充裕堆的个数大于等于2.
1.2: \(T_0\): 充裕堆的个数为0.
ps:这里不存在\(T_1\):充裕堆的个数为1,是因为,各堆的石子的个数亦或和为0.不可能存在
2.1: \(S_0\): 仅有
奇数个孤单堆
2.2: \(S_1\): 仅有
一个充裕堆, 若干个孤单堆
2.3: \(S_2\): 大于等于两个充裕堆,若干个孤单堆,总之亦或和不为0
下面给出若干个性质:
- \(S_2\)无法转移到\(T_0\), 结论很显然
- \(S_0\)为必败态,也是很显而易见的。
- \(T_0\)为必胜态,相当于有偶数个1.
- \(S_1\)为必胜态,因为先手可以调节充裕堆的个数:当有奇数个1时,将充裕堆中的石子全部取走,变成了\(S_0\),先手胜;或者当有偶数个1时,充裕堆只剩下一个石子。
- \(T_2\)为必败态:\(T_2\)只能转移到\(S_2 or S_1\), \(S_2只能转移到T_2\)
- \(S_2\)为必胜态: \(S_2\)只能转移到\(T_2\),\(T_2\)是必败态
综上:\(T_2, S_0是必败态,S_2, S_1, T_0是必胜态\)
AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <bits/stdc++.h> using namespace std; const int maxn = 1e3+10; int num[maxn]; int n; int main(){ int t; ios::sync_with_stdio(false); cin>>t; int kase = 1; while(t--){ cin>>n; int tot = 0; int Xor = 0; int single = 0; for(int i=0; i<n; i++){ cin>>num[i]; if(num[i] == 1) single++; Xor ^= num[i]; } if((single<=n-2&&Xor==0)||(Xor!=0&&(single == n)&&single%2)){ printf("Case %d: Bob\n", kase++); } else { printf("Case %d: Alice\n", kase++); } } return 0; }
|
拆分石子游戏
有n堆石子,每一次只能将一堆石子分成数量不等的两堆。
注意用set来存状态,用vis[]数组会爆空间
AC code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <bits/stdc++.h> using namespace std; const int maxn = 1e4+10; int sg[maxn]; int dfs(int x){ if(sg[x]!=-1) return sg[x]; int mid; set<int> s; s.clear(); if(x%2==0){ mid = x/2-1; } else{ mid = x/2; } for(int i=1; i<=mid; i++){ s.insert(dfs(x-i)^dfs(i)); } for(int i=0; i<=x; i++){ if(s.count(i) == 0){ return sg[x] = i; } } } int main() { ios::sync_with_stdio(false); int t; cin>>t; int kase = 1; memset(sg, -1, sizeof(sg)); while(t--){ int n; cin>>n; int temp; int ans = 0; for(int i=0; i<n; i++){ cin>>temp; ans^=dfs(temp); } if(ans){ printf("Case %d: Alice\n", kase++); } else{ printf("Case %d: Bob\n", kase++); } } return 0; }
|
树上博弈
我们可以得到下面的结论,一棵子树的根节点的SG值为各子子树根节点的SG值加1的亦或和
注意:
当对树的边权进行赋值的时候,前面的树形结构相当于边权为1。后面我们将会看到树带有边权的树上博弈
无向图上面的博弈要先进行相应的缩点,然后再进行树上博弈
有边权的树上博弈
每一次一个人将树上面的边权-1,当边权为0的时候,整棵子树都会被删除
题解
下面的解释我还不太会证明。先记住结论吧
SG定理,对于当前节点u,每次考虑字节点v,u-v边的长度为l
当l为1时:sg(u) ^= (sg(v) + 1)
当l为奇数时: 需要判断sg(v)奇偶性,奇数-1,偶数+1;
当l为偶数时:sg(u) ^= sg(v)
AC代码
棋盘上面的移动石子
通过相应的转换,然后转化为Nim博弈
假设(i, j)坐标上面有石子,如果(i+j)的奇偶性和(n+m)的奇偶性相同,那么后手可以仿照先手的步骤,那么剩下的棋盘的石子就是Nim博弈了
AC code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <bits/stdc++.h> using namespace std; int main(){ int t; scanf("%d", &t); int kase = 1; while(t--){ int r, c; scanf("%d%d", &r, &c); int ans = 0; int temp; for(int i=1; i<=r; i++){ for(int j=1; j<=c; j++){ scanf("%d", &temp); if((r+c-i-j)%2 == 0){ continue; } else{ ans ^= temp; } } } if(ans==0){ printf("Case %d: lose\n", kase++); } else{ printf("Case %d: win\n", kase++); } } return 0; }
|
需要补充的题目
christmas game
未解决的问题