每天一道博弈论之“分裂游戏”(HNOI2007)
题目链接:https://www.luogu.org/problemnew/show/P3185
题解:
首先这道题第一眼望过去好像不是很简单qwq,那么我们先尝试看能不能发现一些特殊性质。
我们先从后手相消的原则去看,则可以发现若一个瓶子中有偶数个的话,那么这个瓶子是没有意义的。因为后手可以完全复制先手的操作,则该瓶子内减去偶数个巧克力豆还是偶数个,新增的两个瓶子内也都是增加了偶数个。
于是我们就只考虑奇数个的瓶子。而且明显只要当成1就可以了,因为取掉1之后就是偶数,就可以当做不存在了。那么原序列就变成了一个01序列。、
假如我们了解过multi-nim模型的话我们就可以发现其实剩下的操作与multi-nim是很像的。
什么是multi-nim呢?
n堆石子,两种操作,其一为在一堆石子中拿走任意个(>0),其二为把一堆石子分成两堆,这两堆石子总数等于原石子数,且都是正整数。不能操作时输。
可以发现每堆石子是独立的。一堆石子数为x的sg值为mex{sg[y],sg[i]^sg[j]}(0<=y<x,i+j==x,i>0,j>0)
我们回到原题。
我们可以观察到每个1所对应的游戏状态是独立的,那么我们算出每个1的sg值再用sg定理即可解决。我们可以把01序列中1的位置对应到multi-nim中的值。比如样例中序列(1,0,1,5000),转换成01序列为(1,0,1)(最后一位不能移动,不需考虑),那么其对应的游戏状态为(1,3),只不过这时与multi-nim不同的一点就是此时一个数分成的两个数之和不一定要等于原数,而且可以为零。
#include<iostream> #include<cstdio> #include<cstring> #define LL long long #define RI register int using namespace std; const int INF = 0x7ffffff ; inline int read() { int k = 0 , f = 1 ; char c = getchar() ; for( ; !isdigit(c) ; c = getchar()) if(c == '-') f = -1 ; for( ; isdigit(c) ; c = getchar()) k = k*10 + c-'0' ; return k*f ; } int n ; int sg[23], a[23] ; int dfs(int x) { if(sg[x]) return sg[x] ; bool v[100] = {0} ; for(int i=x+1;i<=n;i++) for(int j=i;j<=n;j++) v[dfs(i)^dfs(j)] = 1 ; for(int i=0; ;i++) if(!v[i]) { sg[x] = i ; return i ; } } int main() { int t = read() ; while(t--) { n = read() ; memset(sg,0,sizeof(sg)) ; for(int i=1;i<=n;i++) a[i] = read() ; int res = 0 ; for(int i=1;i<n;i++) if(a[i]&1) res ^= dfs(i) ; int tot = 0 ; for(int i=1;i<n;i++) { // if(!(a[i]&1)) continue ; // 虽然我们在转换成multi-nim游戏后不用考虑那些偶数点 // 但从方案的角度考虑先移偶数也可能会到达必败态 for(int j=i+1;j<=n;j++) for(int k=j;k<=n;k++) { if(!(dfs(j)^dfs(k)^dfs(i)^res)) { // 当该位原本为奇数时,dfs(i)^res即删去原位的i // 再亦或上dfs(j)^dfs(k)即在i为与j位加上1 // 当该位原本为偶数时,res表示原游戏sg值 // 移动导致i,j,k三个位置都发生奇偶变化,即新增三个游戏,所以要亦或上三个游戏的sg值 tot ++ ; if(tot == 1) printf("%d %d %d\n",i-1,j-1,k-1) ; } } } if(!tot) printf("-1 -1 -1\n") ; printf("%d\n",tot) ; } return 0 ; }
错误原因:一开始没有考虑到虽然对于胜负来说偶数无所谓,但对于方案来说还是可以移动偶数的。所以只要把那句跳过偶数的注释掉就ok了。
(博弈论主要是个人的思考与理解,很难讲明白,本博客只希望其中的某几句话能激发大家的灵感即可)
——end ;