Warning: session_start(): open(/tmp/sess_83fddb5294bd2bcf30f1cda8e41c66bd, O_RDWR) failed: No space left on device (28) in /data/wiki/inc/init.php on line 239

Warning: session_start(): Failed to read session data: files (path: ) in /data/wiki/inc/init.php on line 239

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/auth.php on line 430
Writing /data/wiki/data/cache/d/de2edb2fcb553ea79b79c722a4e13dbc.captchaip failed

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/actions.php on line 38

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/lib/tpl/dokuwiki/main.php on line 12
2020-2021:teams:legal_string:王智彪:博弈论 [CVBB ACM Team]

用户工具

站点工具


2020-2021:teams:legal_string:王智彪:博弈论

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:王智彪:博弈论 [2021/08/11 17:09]
王智彪
2020-2021:teams:legal_string:王智彪:博弈论 [2021/09/14 11:20] (当前版本)
王智彪
行 147: 行 147:
 比如 $k=3$ : 比如 $k=3$ :
  
-由上,只考虑单枚硬币向上的情况。显然 $sg[1]=sg[2]=0,​sg[3]=mex(sg[2]^sg[1])=1$+由上,只考虑单枚硬币向上的情况。显然 $sg[1]=sg[2]=0,​sg[3]=mex(sg[2]$sg[1])=1$
  
 $N=3t$ 时,先手必赢, $sg[3t]=1$ ,其余必输, $sg[3t+1]=sg[3t+2]=0$ 。 $N=3t$ 时,先手必赢, $sg[3t]=1$ ,其余必输, $sg[3t+1]=sg[3t+2]=0$ 。
  
-因为 $3t$ 翻完之后,会变成 $3t-1$ 和 $3t-2$ 这两种情况的异或和,也就是 $sg[3t]=mex(sg[3t-1]^sg[3t-2])=1$ ,对于 $3t+1$ ,$sg[3t+1]=mex(sg[3t]^sg[3t-1])=mex(1)=0$ ,对于 $3t+2$ ,同理。+因为 $3t$ 翻完之后,会变成 $3t-1$ 和 $3t-2$ 这两种情况的异或和,也就是 $sg[3t]=mex(sg[3t-1]$sg[3t-2])=1$ ,对于 $3t+1$ ,$sg[3t+1]=mex(sg[3t]$sg[3t-1])=mex(1)=0$ ,对于 $3t+2$ ,同理。
  
 推广到别的值也成立。 推广到别的值也成立。
行 224: 行 224:
 ​ return 0; ​ return 0;
  
 +}
 +
 +</​code>​
 +
 +</​hidden>​
 +
 +太屑了,打完上面的表都没找到规律...其实注意到 $2$ 的幂次 $sg$ 值都是 原数的二倍就应该知道和二进制的关系了,硬生生没看出来...规律是二进制为 $1$ 的位如果是奇数个就是 $sg[x]=2x$ ,不然是 $sg[x]=2x+1$ 。
 +
 +然后我们考虑 $sg[x_{1}]$ ^ $sg[x_{2}]$ ^ $…$ ^ $sg[x_{n}]=0$ ,有一个很显然的事,我们先规定二进制为 $1$ 的位数是奇数的数是 $j$ 数,为偶数的数为 $o$ 数。显然, $j$ ^ $j=o$ ^ $o=o,j$ ^ $o=o$ ^ $j=j$ 。
 +
 +注意到, $0$ 是一个 $o$ 数,假设 $sg$ 中 $j$ 的个数为奇数,那么异或起来总共 $1$ 的位数必为奇数,矛盾!所以 $j$ 的个数为偶数。又注意到,不管原来的 $x$ 为什么数, $sg[x]$ 都一定是 $j$ 数,所以我们有结论,这样的 $n$ 一定是奇数。
 +
 +第二个结论,因为最后一位为 $1$ 的位一定是偶数个,所以可以不看,可以认为所有的 $sg[x]=2x$ ,于是都相当于把二进制后面加了个 $0$ ,所以其实是 $x_{1}$ ^ $x_{2}$ ^ $...$ ^ $x_{n}=0$ 。
 +
 +反之,如果我们有 $x_{1}$ ^ $x_{2}$ ^ $x_{n}=0$ ,且 $n$ 为偶数。则我们应该有二进制位为 $1$ 的位数为奇数的数为偶数,偶数减偶数为偶数,所以偶数的也是偶数。所以这些变成 $2x+1$ 末尾异或起来也是 $0$ ,所以也有 $sg[x_{1}]$ ^ $sg[x_{2}]$ ^ $…$ ^ $sg[x_{n}]=0$ 。然后剩下的问题就变成了 $Nim$ 游戏的那一套,只不过必败的条件多了一个硬币向上的数为偶数个。
 +
 +==== 条件六:每次可以连续翻动任意个硬币,至少翻一个 ====
 +
 +初始编号从 $1$ 开始。
 +
 +$sg[x]=mex(0,​sg[x-1],​sg[x-1]$ ^ $sg[x-2],​…,​sg[x-1]$ ^ $…$ ^ $sg[1])$
 +
 +直接打表
 +
 +<hidden 打表>
 +
 +<code cpp>
 +
 +int sg[101],​vis[1005];​
 +int main() {
 + sg[1]=1;
 + for(int i=2; i<=200; i++) {
 + memset(vis,​0,​sizeof(vis));​
 + int tmp=sg[i-1];​
 + vis[0]=1,​vis[tmp]=1;​
 + for(int j=i-2;​j>​=1;​j--) {
 + tmp^=sg[j];​
 + vis[tmp]=1;​
 + }
 + for(int j=0;​j<​1005;​j++) {
 + if(!vis[j]) {
 + printf("​%d %d\n",​i,​j);​
 + sg[i]=j;​
 + break;
 + }
 + }
 + }
 + return 0;
 +}
 +
 +</​code>​
 +
 +</​hidden>​
 +
 +这回找到规律了!就是这个数二进制最后一个为 $1$ 的位代表的值。
 +
 +===== 阶梯 $Nim$ 游戏 =====
 +
 +问题:有 $n$ 个位置 $1,2,…,n$ , $i$ 位置上有 $a_{i}$ 个石子,两个人轮流操作,每次可以挑选任意一个存在石子的位置 $i$ ,将至少一个石子移动到 $i-1$ 的位置(所以终局是所有石子都移动到了 $0$ 这个位置),谁不能移动就输。
 +
 +显然这个是 $Nim$ 游戏的改版,我们将奇数位置挪动到偶数位置看成减少石子,偶数位置移动到奇数位置看成增加石子,也就是说我们只关心奇数位置上的石子。然后就看成简单的 $Nim$ 游戏就可以了。
 +
 +比如最开始奇数位置异或起来不为零。由 $Nim$ 游戏,先手可以移动某一奇数位置的石子,让这个异或和为零,然后后手无论怎么操作,先手都让异或和维持为零,然后又因为所有石子距离零这个位置的总距离总在不断减少,所以游戏早晚会结束,所以结论得证。
 +
 +===== 树上删边游戏 =====
 +
 +给出一个 $N$ 个点的有根树,轮流删边,并且将不与根节点相连的部分移走,无边可删者输。
 +
 +显然叶子节点的 $sg$ 值为 $0$ 。开始构造其他简单情况。
 +
 +注意到一个结点连接奇数个叶子节点的 $sg$ 值不应为 $0$ ,连接偶数个叶子结点的 $sg$ 值为 $0$ 。所以貌似父亲的 $sg$ 值不是所有儿子的 $sg$ 值的简单异或,但好像又有联系。
 +
 +比如一个结点连接两个儿子,两个儿子的子树都是一条链,左边长度为 $lenl$ ,右边长度为 $lenr$ 。当 $lenl==lenr$ 时先手必败,好像是异或为 $0$ 的情况,反之必胜,好像是异或值不为 $0$ 的情况。
 +
 +注意到一条链长为 $L$ 的话, $sg$ 值为 $L$ 。
 +
 +又比如一个根节点,有三个儿子, $len1=0,​len2=1,​len3=2$ (指链长),这个异或和肯定不为 $0$ ,但是先手必败,注意到都加一的异或和好像是 $0$ ,然后这么猜想一下 $×$ ,就能猜到结论了:父亲的 $sg$ 值是所有儿子 $sg+1$ 异或起来。然后树形 $dp$ 一下就好了。复杂度是 $O(N)$ 的。
 +
 +证明如下:
 +
 +注意到 $sg$ 值一样的游戏是可以相互转化的,如果我们求出来儿子的 $sg$ 值(类似于归纳法),我们就将其等价于一个长度为此 $sg$ 值的链,然后注意到父亲到儿子还有一条边,这边的 $sg$ 值就是儿子 $sg$ 值加一,然后所有的游戏互相独立,所以就是异或起来就可以了。
 +
 +===== 图删边游戏 =====
 +
 +刚才的树变成了图,其余规则不变。
 +
 +相比于树,图多了一些环,有些是奇环,有些是偶环。
 +
 +对于偶环,删掉一个边之后,剩下的两个链长度不同奇偶,所以异或之后的值不可能为 $0$ ,所以 $sg$ 值必为 $0$ ,所以等效于一个点的情况( $sg$ 值也为 $0$ )
 +
 +对于奇环,删掉一个边之后,剩下的两个链长度同奇偶,所以异或的 $sg$ 值不可能为奇数,又因为可以拆成两个长度相同的链,所以 $mex$ 值必为 $1$ ,所以相当于一个长度为 $1$ 的链。剩下就变成上一个问题了。
 +
 +===== 威佐夫博弈 =====
 +
 +问题:两堆糖果,分别有 $n,m$ 个,两个人轮流,可以从某一堆取走至少一个,也可以从两堆中取走同样多的物品,都是至少取一个,最后取光者获胜。
 +
 +手动玩一下发现 $(1,1)$ 是必胜的, $(1,2)$ 是必败的,剩余的 $(1,m)$ 都是必胜的,然后除了 $(2,1)$ 都是必胜的,因为都可以变为 $(2,1)$ 的局面。其实到这里应该发现,我们如果找到必败态,所有能够到达必败态的状态都是必胜态,然后如果到达一个状态,如果这个状态没有打上必胜标记,那他就是必败的,然后他所到达的一切状态都是必胜的。
 +
 +这好像似曾相识?没错就是埃氏筛,没打上必胜标记就对应质数。然后获得新质数之后就把所有的能到达的都变成必胜态(合数)。于是可以在 $O(n^{2})$ 的时间内筛出来所有的必胜必败态。但是威佐夫博弈的板子上的数据范围巨大,巨大到 $O(n)$ 都做不了,这需要一些结论。
 +
 +这里的结论是 $(a_{i},​b_{i})=(a_{i},​a_{i}+i),​i≥1,​a_{1}=1$ ,因为差恒定的都会被提前扫掉,所以每个解的差值都不一样。并且一定是当前没出现的最小的数和这个数加上这个差值。
 +
 +这里有一个很漂亮的式子,是 $a_{i}=(int)(i/​fi),​b_{i}=(int)(i/​fi/​fi)$ ,其中 $fi$ 是 $(sqrt(5)-1)/​2$ ,也就是黄金分割率。于是验证威佐夫博弈是否必胜,只需要先看两堆的差值,然后除以 $fi$ 看是不是 $a$ 就可以了。代码中 $0$ 代表必败, $1$ 代表必胜。
 +
 +<hidden 代码>
 +
 +<code cpp>
 +
 +#​include<​bits/​stdc++.h>​
 +using namespace std;
 +typedef long long ll;
 +const double fi=(sqrt(5)-1)/​2;​
 +ll a,b;
 +int main() {
 + scanf("​%lld %lld",&​a,&​b);​
 + if(a>b) swap(a,b);
 + if((int)((b-a)*1.0/​fi)!=a) puts("​1"​);​
 + else puts("​0"​);​
 + return 0;
 +}
 +
 +</​code>​
 +
 +</​hidden>​
 +
 +这里引申出一个贝蒂定理:如果 $a,b$ 是正无理数,且 ${\frac 1 a}+{\frac 1 b}=1$ ,则 $(int)[na]$ 和 $(int)[nb]$ 可以取遍所有的正整数。这个是高中数竞的内容这里不证明了。在这里 $a={\frac 1 {fi}},​b={\frac 1 {fi^{2}}}$ 。所以包含了所有的必败解。 ​
 +
 +===== 对称性构造 =====
 +
 +现在有 $n$ 个硬币围成一圈,每个硬币放在一个凹槽里,操作是每次可以选 $1$ 到 $k$ 个连续硬币取走。取完的赢,问先后手谁赢。
 +
 +分类讨论:
 +
 +1. $k≥n$ ,先手必胜,直接全部取走就可以了。
 +
 +   
 +
 +2. $k==1$ ,根据 $n$ 的奇偶性, $n$ 奇先手赢,偶后手赢
 +
 +   
 +
 +3. 其余的 $k$ ,都是后手赢。
 +
 +关于第三种情况,为什么?
 +
 +先手显然不会一下子取走一大半,这时后手可以把剩下的都取走。
 +
 +那么不妨设先手取走一小部分,然后后手只需要在剩下圆弧的中间取走一个或两个(根据剩余数量的奇偶性而定),然后剩余两个部分是完全相同的就可以了,这时两边是对称的,先手怎么做,后手在另一堆怎么做就可以了,于是后手必胜,证毕。
 +
 +===== 二分图博弈 =====
 +
 +两个人在二分图上玩游戏,每次一个人沿边移动棋子,棋子待过的点不能再走,不能走的输,问谁赢。
 +
 +结论:考虑二分图最大匹配,如果最大匹配一定包含起点,先手胜,此外后手胜利。然后只需要算一下包含和不包含这个点的最大匹配相不相等即可。
 +
 +然后如果最大匹配一定包含这个点,对于先手,沿着最大匹配走,如果后手能走到另一个点结束,先手走不动,说明那个点完全可以代替这个点,则这个点并不是最大匹配所必须包含的,然后后手走完先手必然还能走,如果这个点没有匹配的,也说明起点不是最大匹配所必须包含的,也矛盾,所以走到的这个点必然可以进行匹配,则先手可以继续走,周而复始,后手早晚会走不动,所以先手必胜,证毕。
 +
 +反之,如果可以不包含这个点,这个点走出去的点,一定在最大匹配中,不然加上这两个点最大匹配更大了,矛盾,则由上,后手必胜,所以先手必败,证毕。
 +
 +===== 斐波那契博弈 =====
 +
 +一堆石子有 $n$ 个,两人轮流取,先取者第一次可以取任意多个,但是不能取完,以后每次取的石子数不能超过上次取石子数的二倍,取完者赢。
 +
 +结论:先手胜当且仅当 $n$ 不是斐波那契数。
 +
 +因为 $i=2$ 时,先手只能取 $1$ 颗,显然必败,结论成立。
 +
 +假设当取到斐波那契的第 $k$ 项都已经成立:
 +
 +当为 $k+1$ 项时,可以拆分为 $f_{k}+f_{k-1}$ ​
 +
 +如果拿走的石子数大于等于 $f_{k-1}$ ,则可以直接拿走剩下的,因为 $f_{k}≤2f_{k-1}$
 +
 +不然,我就先玩完 $f_{k-1}$ 的游戏,由归纳法知,一定是我结束了 $f_{k-1}$ 的游戏,所以接下来剩 $f_{k}$ ,又是你先玩,并且,我这一下最多拿走 ${\frac 2 3}f_{k-1}$ ,所以你最多拿 ${\frac 4 3}f_{k-1}<f_{k}$ ,这个可以由归纳法证明,所以你一次拿不完,由归纳法知你输了,所以 $k+1$ 的情况也递归出来了,所以得证。
 +
 +不是斐波那契数列的情况由某定理可知,我可以将其分为若干个斐波那契项之和,每次取尽量大的。因为每次都取尽可能大的,所以这些斐波那契项必不相邻,所以相邻的倍数差两倍以上。然后先手取完这个最小的斐波那契数那么多。后手显然只能在倒数第二堆里取,且取不完,然后根据上面的结论,我一定可以取走这一堆中的最后一个石子,然后你又要面对下一堆...,这样每一堆的最后一个石子都是我取走的,所以最后也是我取走的,所以先手必胜,得证。
 +
 +===== 代码练习 =====
 +
 +1.[[https://​codeforces.com/​gym/​103119/​problem/​G|2020ACM-ICPC澳门区域赛G]]
 +
 +题目大意:给定一个长度为 $n,​1≤n≤200000$ 的数组和 $m,​1≤m≤200000$ 个操作,每个数的范围为 $[0,255]$ 。
 +
 +操作 $1$ 是在数组末尾添加一个数,操作 $2$ 是再给一个位置,问从这个位置按照规则两人博弈谁能获得胜利。
 +
 +博弈规则是,每次只能选下标比当前数大的数字并且这个数字的二进制要和原来数的二进制不同的位数不能超过 $1$ ,跳到那个位置,谁不能跳谁输。
 +
 +注意到数字较小,我们可以对值进行操作。又发现对于出现了很多次的同一个数字,如果我们当前所在位置不是这个数最后一次出现的位置,则这个位置是必胜态,因为如果这个数出现的最后一个位置是必败态,则我们可以跳到这个位置,于是现在这个位置必胜。如果最后那个位置是必胜态,那说明后面必存在必败态,我们只需要到达那个必败态就可以了。
 +
 +综上,如果不是这个值最后一次出现的位置,都是必胜态。
 +
 +然后对于这个值最后一次出现的位置,我们看看他能到哪些值,这些值都是可以预处理的,然后因为上面的结论,我们不可能选择去能到达值的非最终位置的,因为相对于我来说是必败的,所以我们也是去这些值的最后位置,所以我们只需要记录每个值最后一次出现的位置就可以了。
 +
 +然后对于查询的位置,先看是不是最后一个位置,不是的话直接输出先手必胜。是最后一个位置就 $dfs$ 一下能到达哪些位置,然后记忆化搜索一下,看看有没有必败态就可以了。
 +
 +<hidden 代码>
 +
 +<code cpp>
 +
 +const int maxn=400100,​maxm=260;​
 +vector<​int>​ nxt[260];
 +int n,m;
 +int las[maxm],​a[maxn],​vis[maxm];​
 +bool ok[maxm];
 +
 +bool dfs(int pos) {
 + if(vis[a[pos]]) return ok[a[pos]];
 + vis[a[pos]]=true;​
 + bool flag=false;
 + for(int i=0;​i<​nxt[a[pos]].size();​i++) {
 + if(las[nxt[a[pos]][i]]<​las[a[pos]]) continue;
 + flag|=(!dfs(las[nxt[a[pos]][i]]));​
 + }
 + return ok[a[pos]]=flag;​
 +}
 +
 +int main() {
 + scanf("​%d %d",&​n,&​m);​
 + for(int i=0;​i<​=255;​i++) {
 + for(int j=0;​j<​8;​j++) {
 + nxt[i].push_back(i^(1<<​j));​
 + }
 + }
 + for(int i=1;​i<​=n;​i++) {
 + scanf("​%d",&​a[i]);​
 + las[a[i]]=i;​
 + }
 + for(int i=1,​op,​key;​i<​=m;​i++) {
 + scanf("​%d %d",&​op,&​key);​
 + if(op==1) {
 + a[++n]=key;​
 + las[a[n]]=n;​
 + }else {
 + if(las[a[key]]!=key) {
 + puts("​Grammy"​);​
 + continue;​
 + }
 + memset(vis,​0,​sizeof(vis));​
 + memset(ok,​0,​sizeof(ok));​
 + bool flag=dfs(key); ​
 + printf("​%s\n",​flag?"​Grammy":"​Alice"​);​
 + }
 + }
 + return 0;
 +}
 +
 +</​code>​
 +
 +</​hidden>​
 +
 +2.[[https://​ac.nowcoder.com/​acm/​contest/​11166/​A|2021牛客1A]]
 +
 +题目大意:给定两堆石子,有两个人,每次可以在两堆中分别取 $k$ 和 $s×k$ 个石子,其中 $k>0,​s≥0$ 。谁先取完谁赢,问先后手谁必胜。石子数量都不超过 $5000$ 。
 +
 +这个数据范围大概率是筛出来的,仿照前面的威佐夫博弈和线性筛思想打个表就好了,对于必败态,能到达必败态的都是必胜态。
 +
 +<hidden 题解>
 +
 +<code cpp>
 +
 +#include <​bits/​stdc++.h>​
 +using namespace std;
 +typedef long long ll;
 +
 +const int maxn=5000;
 +bool dp[5010][5010];​
 +
 +void get_ab() {
 + for(int i=0;​i<​=maxn;​i++) {
 + for(int j=0;​j<​=maxn;​j++) {
 + if(!dp[i][j]) {
 + for(int k=1;​k<​=maxn;​k++) {
 + if(i+k>​maxn&&​j+k>​maxn) break;
 + for(int s=0;​s<​=maxn;​s++) {
 + if(i+k*s>​maxn&&​j+k*s>​maxn) break;
 + if(i+k*s<​=maxn&&​j+k<​=maxn) dp[i+k*s][j+k]=true;​
 + if(i+k<​=maxn&&​j+k*s<​=maxn) dp[i+k][j+k*s]=true;​
 + }
 + }
 + }
 + }
 + }
 +}
 +
 +int main() {
 + get_ab();
 + int t,a,b;
 + scanf("​%d",&​t);​
 + while(t--) {
 + scanf("​%d %d",&​a,&​b);​
 + if(dp[a][b]) puts("​Alice"​);​
 + else puts("​Bob"​);​
 + }
 + return 0;
 +}
 +
 +</​code>​
 +
 +</​hidden>​
 +
 +ps:​这玩意本地跑得太慢了,还是牛客的机器好用 $×$
 +
 +3.[[https://​ac.nowcoder.com/​acm/​problem/​15066|小牛再战]]
 +
 +题意:共有 $n$ 堆石子,两个人轮流取石子,每次只能选择 $n$ 堆石子中的一堆取一定数量(至少一个)的石子,取过之后,还可以将这堆石子中剩余的石子随意取几个放在其他任意堆上,取完石子的人获胜,一堆石子没有子之后是不能放的。问先手能不能赢。
 +
 +题解:给出结论:当所有堆都有配对的堆(指石子数一样的两堆石子)的时候,先手必败,不然先手必胜。
 +
 +因为如果都配对了,先手做什么后手只需要拿另一堆石子开刀做几乎完全一样的事情就可以继续使石子都配对,这样先手必败,不完全一样的事情是比如 $a,b$ 两堆石子配对数量一样, $a$ 拿走之后又取了一堆之后消失了,这样 $b$ 还给 $a$ 的那部分可以归到取走里,这样还是配对的。
 +
 +如果不完全配对,我们选择出现奇数次中最大的石子堆,比如现在是 $2\ 6\ 8\ 9$ ,然后拿 $9$ 这一堆,至少拿一个变成 $8$ ,然后我们把 $6$ 这堆补到 $8$ ,然后 $8$ 变成 $6$ ,再接着找更少的,如果最开始奇数的堆数是偶数,就把自己保留到最少的那堆石子数,不然剩下的也取走,这样保证所有都配对,后手必败,所以先手必胜。
 +
 +<hidden 代码>
 +
 +<code cpp>
 +
 +#include <​bits/​stdc++.h>​
 +using namespace std;
 +typedef long long ll;
 +
 +int n,​tong[101];​
 +
 +int main() {
 + while(~scanf("​%d",&​n)&&​n) {
 + memset(tong,​0,​sizeof(tong));​
 + for(int i=1,​tmp;​i<​=n;​i++) {
 + scanf("​%d",&​tmp);​
 + tong[tmp]++;​
 + }
 + int flag=0;
 + for(int i=1;​i<​=100;​i++) {
 + if(tong[i]&​1) {
 + flag=1;
 + break;
 + }
 + }
 + if(flag) {
 + puts("​Win"​);​
 + }else puts("​Lose"​);​
 + }
 + return 0;
 } }
  
2020-2021/teams/legal_string/王智彪/博弈论.1628672987.txt.gz · 最后更改: 2021/08/11 17:09 由 王智彪