组合游戏
Nim游戏的一个变形
题解请看金海峰的博客
以下为引用:
分析:我们把棋子按位置升序排列后,从后往前把他们两两绑定成一对。如果总个数是奇数,就把最前面一个和边界(位置为0)绑定。 在同一对棋子中,如果对手移动前一个,你总能对后一个移动相同的步数,所以一对棋子的前一个和前一对棋子的后一个之间有多少个空位置对最终的结果是没有影 响的。于是我们只需要考虑同一对的两个棋子之间有多少空位。我们把每一对两颗棋子的距离(空位数)视作一堆石子,在对手移动每对两颗棋子中靠右的那一颗 时,移动几位就相当于取几个石子,与取石子游戏对应上了,各堆的石子取尽,就相当再也不能移动棋子了。
我们可能还会考虑一种情况,就是某个玩家故
意破坏,使得问题无法转换为取石子,例如前一个人将某对中的前者左移,而当前玩家不将这对中的另一移动,则会导致本堆石子增多了,不符合nim。但是这种
情况是不会出现的。因为赢家只要按照取石子进行即可获胜,而输家无法主动脱离取石子状态。如果输家想要让某堆石子增多,那么赢家只需要让该堆减少回原状,
这样输家又要面临跟上一回合同样的局面。
1 //POJ 1704 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<iostream> 6 #include<algorithm> 7 #define rep(i,n) for(int i=0;i<n;++i) 8 #define F(i,j,n) for(int i=j;i<=n;++i) 9 #define D(i,j,n) for(int i=j;i>=n;--i) 10 using namespace std; 11 void read(int &v){ 12 v=0; int sign=1; char ch=getchar(); 13 while(ch<'0'||ch>'9'){ if (ch=='-') sign=-1; ch=getchar();} 14 while(ch>='0'&&ch<='9'){ v=v*10+ch-'0'; ch=getchar();} 15 v*=sign; 16 } 17 /******************tamplate*********************/ 18 int n,f[1010]; 19 int main(){ 20 freopen("1704.in","r",stdin); 21 int t; 22 read(t); 23 while(t--){ 24 read(n); 25 rep(i,n) read(f[i]); 26 if(n&1) f[n++]=0; 27 sort(f,f+n); 28 int ans=0; 29 for(int i=n-1;i>0;i-=2) 30 ans^=f[i]-f[i-1]-1; 31 if(ans) printf("Georgia will win "); 32 else printf("Bob will win "); 33 } 34 return 0; 35 }
我的错误思路:
简单的情况:
所有的棋子都挨在一起
那么若有偶数个棋子,则后手胜(2个棋子的话,只能动最靠左的一个,那么无论先手将左1移到哪里,后手都将左2移到紧挨左1的位置,递推可知4、6、8等偶数都是后手胜)
反之先手胜
复杂一点:
所有的棋子分散形成一些连通块,每块有奇数或偶数个棋子(这不是废话吗……),每次移动必然改变两个连通块的奇偶性(移成单独一个棋子相当于新建了一个连通块,那么可以视为将一个连通块从0变成1(偶变奇))
那么: 偶和偶在一起还是偶(后手胜)
奇和奇在一起变成偶(后手胜)
奇和偶在一起还是奇(先手胜)
特判:从左端开始连出来的连通块不能算进来
所以答案就是:数一共有多少个奇连通块,若有奇数个,先手胜;偶数个,后手胜。
先手必胜策略:将第一个奇连通块的第一个棋子移到左端(尽可能靠左),之后按后手策略下“模仿棋”
后手必胜策略:对手若移动一个棋子A,则将A后面的第一个棋子B移到A旁边
先手自己作死的方法:将1、2变成1、1、1
后手作死:同上
以上思路为错误思路
sigh......
Update:
奇+奇--->偶+偶 :先手胜
奇+奇--->奇+奇+偶 :
偶+偶--->
先手必败状态:只有偶连通块