• 博弈论专题


    知识基础:Nim游戏与SG函数

    参考:[学习笔记] (博弈论)Nim游戏和SG函数_A_Comme_Amour的博客-CSDN博客_nim博弈

    首先定义P是先手必败,N是先手必胜

    有以下性质:

    那么终结位置是P

    可以转移到P的是N

    所有操作都到达N的是P

    那么根据以上三点,可以暴力求某一个局面是N还是P。步骤是先使得终结位置是P,然后枚举状态,能到P的是N。然后所有都到达N的P。然后不断迭代。

    我做过的一些博弈题就是这个思路。

    但是Nim游戏这么做的话时间复杂度很高。

    Nim游戏的结论是异或和为0的情况下先手必败。

    异或和为0和异或和不为0都可以通过一步操作互相转化。

    sg函数:定义mex()表示集合中最小的为出现的非负整数。sg[x] = mex(sg[y] | y是x的后继)

    那么终结位置没有后继,sg为0,其他的值可以通过定义算出。

    sg = 0是先手必败,否则先手必胜。

    多个子游戏的sg值是它们的异或和。

    sg的计算:一堆石子,若操作石子数为1~m,那么sg[x] = x % (m + 1)

    若为非零任意数,那么sg[x] = x

    其他,根据定义暴力计算即可。

    例题

    比较裸,看作多个子游戏,每个子游戏计算sg值,看异或和即可

    阶梯博弈

    首先偶数层的石子是没有用的,因为一个人移动了偶数层的石子,另一个人可以模仿把移到奇数层的又移到偶数层。奇数层的操作,石子到了偶数层,相当于扔掉了。所以可以只看奇数层,奇数层的石子组成了一个Nim游戏。对于有必胜策略的那个人,对面移动奇数堆,我就按照Nim游戏移,对面移偶数堆,我就模仿它。

    Great Party(Nim拓展+莫队)

    在nim游戏的基础上,加了一个可以把剩余石子合并到其他堆的操作

    如果是1堆,先手直接拿完,先手必胜

    如果是两堆,从简单的开始。1 1,那么每次操作没得选,后手赢。

    1 x,x>1  先手可以转化到1 1,先手赢

    x x 这时每次操作都不能合并,合并就输了,这时后手可以模仿先手的操作,最后后手拿完,后手赢。

    x y ,x!= y 先手可以转化到x x,先手赢

    总结,2堆的时候,相同后手赢,不相同先手赢

    3堆,发现这时先手可以转化为x x的情况,先手赢

    4堆,这时一定不能合并,一合并就变成三堆,对手面对这种情况必赢

    所以最后一定是1 1 1 1。那么每个数减去1,就变成了标准的nim游戏了

    此时若异或和为0,后手操作完,先手面对1 1 1 1,此时后手赢。反之,异或和不为0,先手1

    因此4堆的情况,减去1后,异或和为 0,后手赢,异或和不为0,先手赢。我们可以发现这个结论是包含2堆的情况的。

    因此我们可以猜结论,奇数堆是先手,偶数堆,减去1后,异或和不为0是先手,否则后手。

    为了简便,我们计算后手赢的情况,也就是长度为偶数且异或和为0

    因为异或和不为0不好判断,异或和为0的话转化成前缀也就是前缀异或和相同,这个可以用莫队。

    1. #include
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. typedef long long LL;
    6. const int N = 1e5 + 10;
    7. struct query
    8. {
    9. int l, r, id, bl;
    10. }q[N];
    11. int s[N], n, m;
    12. LL ans[N], cur;
    13. unordered_map<int, int> cnt[2];
    14. bool cmp(query x, query y)
    15. {
    16. if(x.bl != y.bl) return x.bl < y.bl;
    17. if(x.bl % 2 == 1) return x.r < y.r;
    18. return x.r > y.r;
    19. }
    20. void add(int x)
    21. {
    22. int id = x % 2;
    23. x = s[x];
    24. cur += cnt[id][x];
    25. cnt[id][x]++;
    26. }
    27. void erase(int x)
    28. {
    29. int id = x % 2;
    30. x = s[x];
    31. cnt[id][x]--;
    32. cur -= cnt[id][x];
    33. }
    34. int main()
    35. {
    36. scanf("%d%d", &n, &m);
    37. int block = sqrt(n);
    38. _for(i, 1, n)
    39. {
    40. int x; scanf("%d", &x);
    41. x--;
    42. s[i] = s[i - 1] ^ x;
    43. }
    44. _for(i, 1, m)
    45. {
    46. int l, r;
    47. scanf("%d%d", &l, &r);
    48. l--;
    49. q[i] = {l, r, i, l / block};
    50. }
    51. sort(q + 1, q + m + 1, cmp);
    52. int l = 1, r = 0;
    53. _for(i, 1, m)
    54. {
    55. int ll = q[i].l, rr = q[i].r;
    56. while(l < ll) erase(l++);
    57. while(l > ll) add(--l);
    58. while(r > rr) erase(r--);
    59. while(r < rr) add(++r);
    60. LL len = rr - (ll + 1) + 1;
    61. ans[q[i].id] = len * (len + 1) / 2 - cur;
    62. }
    63. _for(i, 1, m) printf("%lld\n", ans[i]);
    64. return 0;
    65. }

    Z-Game on grid(dp)

    这个不是标准的博弈,但是很像博弈。A可以控制自己的行为,B可以理解为随机走。

    从结果逆推,当A操作时,只要有一个操作可以达到即可,B操作时,要全部都达到

    有点像暴力算N状态和P状态

    1. #include
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 500 + 10;
    6. int dp[N][N][3], n, m; //0 A 1 D 2 B
    7. char s[N][N];
    8. int main()
    9. {
    10. int T; scanf("%d", &T);
    11. while(T--)
    12. {
    13. scanf("%d%d", &n, &m);
    14. _for(i, 1, n) scanf("%s", s[i]);
    15. _for(i, 1, n + 1)
    16. _for(j, 1, m + 1)
    17. rep(k, 0, 3)
    18. dp[i][j][k] = 0;
    19. for(int i = n; i >= 1; i--)
    20. for(int j = m; j >= 1; j--)
    21. {
    22. if(s[i][j] == 'A') dp[i][j][0] = 1;
    23. else if(s[i][j] == 'B') dp[i][j][2] = 1;
    24. else if(i == n && j == m) dp[i][j][1] = 1;
    25. else
    26. {
    27. rep(k, 0, 3)
    28. {
    29. if((i + j) % 2 == 0) dp[i][j][k] = (dp[i + 1][j][k] || dp[i][j + 1][k]);
    30. else dp[i][j][k] = ((i + 1 > n || dp[i + 1][j][k]) && (j + 1 > m || dp[i][j + 1][k]));
    31. }
    32. }
    33. }
    34. rep(k, 0, 3) printf(dp[1][1][k] ? "yes " : "no ");
    35. puts("");
    36. }
    37. return 0;
    38. }

    Split Game(暴力计算SG函数)

    这题就是暴力计算SG函数

    先把边界条件,sg=0先手必败初始化后

    然后遍历每一个状态,对于每一个状态枚举它的所有后继,计算后继的sg值

    这道题的操作是分解成两个子游戏,这时的sg值是它们的异或。

    1. #include
    2. #define rep(i, a, b) for(int i = (a); i < (b); i++)
    3. #define _for(i, a, b) for(int i = (a); i <= (b); i++)
    4. using namespace std;
    5. const int N = 160;
    6. int sg[N][N], a[N << 1];
    7. int check(int i, int j)
    8. {
    9. return i == 1 && j == 1;
    10. }
    11. int main()
    12. {
    13. memset(sg, -1, sizeof sg);
    14. sg[1][2] = sg[2][1] = 0;
    15. sg[1][3] = sg[3][1] = 0;
    16. _for(i, 1, 150)
    17. _for(j, 1, 150)
    18. {
    19. if(i == 1 && j == 1 || !sg[i][j]) continue;
    20. memset(a, 0, sizeof a);
    21. _for(k, 1, i - 1)
    22. {
    23. if(check(k, j) || check(i - k, j)) continue;
    24. a[sg[k][j] ^ sg[i - k][j]] = 1;
    25. }
    26. _for(k, 1, j - 1)
    27. {
    28. if(check(i, k) || check(i, j - k)) continue;
    29. a[sg[i][k] ^ sg[i][j - k]] = 1;
    30. }
    31. rep(k, 0, N << 1)
    32. if(!a[k])
    33. {
    34. sg[i][j] = k;
    35. break;
    36. }
    37. }
    38. int n, m;
    39. while(~scanf("%d%d", &n, &m))
    40. puts(sg[n][m] ? "Alice" : "Bob");
    41. return 0;
    42. }

  • 相关阅读:
    easyExcel按模板填充数据,处理模板列表合并问题等,并导出为html,pdf,png等格式文件demo
    Kubernetes常用命令
    Vuex的核心组成、版本问题及store.js的使用、 Vuex中存值、取值以及获取变量值、异步同步操作和Vuex后台交互
    MySQL 45 讲 | 09 普通索引和唯一索引,应该怎么选择?
    [LeetCode]剑指 Offer 14- II. 剪绳子 II
    SQL按照id集合顺序返回
    vue3 canvas验证码和滑块拼图验证
    ChainForge:衡量Prompt性能和模型稳健性的GUI工具包
    大气污染扩散模型Calpuff实践
    神通MPP数据库的跨库查询
  • 原文地址:https://blog.csdn.net/qq_34416123/article/details/126369178