Sicily 1922. Winning Ways,sicilywinning
題目
尼姆博奕(Nimm Game)
兩個人從N堆石子中的任意一堆任取1個或多個,取到沒得取的人為負者。
奇異局勢
如果面對一種局勢的人是必敗的,那麼這種局勢叫做奇異局勢。比如如果N堆石子都是0個,那麼先取者面對的(0,0,0,…,0)就是一種奇異局勢。
如果我想讓我的對手輸,我會想盡辦法讓他面對奇異局勢。
奇異局勢有這樣一種性質,比如現在的N堆石子的數目為a1,a2,a3,…,aN,那麼:
a1⊕a2⊕a3⊕…⊕aN=0。
a1⊕a2⊕a3⊕…⊕aN一般稱為Nim-sum。
為什麼Nim-sum=0的局勢是奇異局勢?
也就是說,為什麼當玩家面對Nim-sum=0的時候,他是必輸的?
這篇論文有比較好的解釋:
http://web.mit.edu/sp.268/www/nim.pdf
下面試著翻譯為中文:
證明:如果一個玩家想在尼姆博奕的遊戲中勝出,他應該讓Nim-sum值為0。
引理1 如果在某一輪中Nim-sum=0,那麼下一次的操作必然會改變Nim-sum。
證:
令x1,x2,…,xn表示操作前N堆石子的個數,s表示操作前N堆石子的Nim-sum,那麼:
s=x1⊕x2⊕x3⊕…⊕xn;
令y1,y2,…,yn表示操作後N堆石子的個數,t表示操作後N堆石子的Nim-sum,那麼:
t=y1⊕y2⊕y3⊕…⊕yn;
令操作中改變的石子堆標號為k,那麼在操作後必然有xk!=yk,且對於i!=k來說必然有xi=yi,說白了就是改變一個且只有一個石子堆。
那麼:
t = 0 ⊕ t
= s ⊕ s ⊕ t
= s ⊕ (x1 ⊕ x2 ⊕ … ⊕ xn) ⊕ (y1 ⊕ y2 ⊕ … ⊕ yn)
= s ⊕ (x1 ⊕ y1) ⊕ (x2 ⊕ y2) ⊕ … ⊕ (xk ⊕ yk)
= s ⊕ xk ⊕ yk
由於xk!=yk,所以xk ⊕ yk != 0,而我們知道在操作前的Nim-sum=0,也就是s為0,所以t != 0,證畢。
引理2 如果在某一輪中Nim-sum != 0,那麼下一次的操作可以改變Nim-sum使其為0。
證:
以下字母除了說明都與引理一定義相同。
令d是s的最高有效位,我們選取一個石子堆xk,xk的最高有效位也是d(這是一定可以找得到的,因為s=x1⊕x2⊕x3⊕…⊕xn)。
那麼令yk = s ⊕ xk,由於最高有效位相同,s ⊕ xk的值必然小於等於xk,比如 s = 100010,
xk= 101010,
此時yk= 001000≤xk。
所以我們總能夠從xk中拿走xk − yk個石子使得yk = s ⊕ xk。
那麼t = s ⊕ xk ⊕ yk(由引理一)
= s ⊕ xk ⊕ xk ⊕ s
= s ⊕ s ⊕ xk ⊕ xk
= 0
證畢。
現在我們來回答開頭的問題:為什麼當玩家面對Nim-sum=0的時候,他是必輸的?
當玩家A面對Nim-sum=0的時候,由引理1,他必然會使得Nim-sum!=0;由引理2,下一輪玩家B面對Nim-sum!=0時,他完全可以使Nim-sum=0。
如此迴圈往複,最後石子堆的情況將會是Nim-sum=0的極限情況,也就是a1=0,a2=0,…,an=0。
那麼玩家A輸定了。
思路
所以,這題中的問題是先手玩家有幾種方法可以在第一輪操作後的石子堆為Nim-sum=0的狀態,其實也就是引理二中的內容了。
先將遊戲剛開始的Nim-sum算出來;
如果Nim-sum=0,那麼先手玩家面對的就是奇異局勢,他根本沒有辦法贏,方法數為0;
否則,由引理2,統計(Nim-sum ^ xi) <= xi的個數就是贏的方法數。
代碼
#include <stdio.h>int main() { int CaseNum; scanf("%d\n", &CaseNum); while (CaseNum--) { int N, Sum = 0, Stones[50005], k = 0, XorSum = 0; scanf("%d\n", &N); char C[600000]; gets(C); // Use the gets() function to read a line with lots of datas is fast than scanf() for (int i = 0; C[i] != '\0'; i++) { if (C[i] == ' ') { Stones[k++] = Sum; Sum = 0; } else { Sum = Sum * 10 + C[i] - '0'; } } Stones[k] = Sum; for (int i = 0; i < N; i++) XorSum ^= Stones[i]; if (XorSum == 0) printf("0\n"); else { int Ways = 0; for (int i = 0; i < N; i++) if ((XorSum ^ Stones[i]) <= Stones[i]) Ways++; printf("%d\n", Ways); } } return 0;}