1. 程式人生 > >【SG函式 && 推導】HDU

【SG函式 && 推導】HDU

Step1 Problem:

給你 n 堆石子,每堆石子的個數分別為 a[i]。
Nim 博弈的基礎上多一個條件:可以將一堆石頭分成三堆,每堆至少得大於 0,問你先手贏,還是後手
資料範圍:
1<=n<=1e6, 1<=a[i]<=1e9.

Step2 Ideas:

正常情況的 Nim 博弈每堆石子的 sg函式值 = 石子的個數。
現在多了條件:將一堆石頭分成三堆。
x = x1+x2+x3, 要想 x1^x2^x3 = x,x 的二進位制位的 1 的個數必須大於 2 個。
當石子堆個數是 7 的時候,二進位制對應 111,這時候 sg[7] = 8。
當石子堆個數是 8 的時候,二進位制對應 1000,能否 8 = x1+x2+x3, sg[x1]+sg[x2]+sg[x3] = 7 呢?你發現這是不能的(演算紙上動動筆就懂了)。所以 sg[8] = 7。
當石子堆個數是 9~14 的時候,要想分成三堆 sg[] 異或 = 自身,其中一堆的 sg[] 必須大於 7,如果分成三堆中有 7 的話,就必須有 8,顯然 7 是不能要的。這樣其中一堆就必須大於 8,你發現這時已經至少有兩個 1 了,所以並不能分成三堆 sg[] 異或 = 自身, sg[9~14] = 自身


當石子堆個數是 15 的時候,二進位制對應 1111,因為 1 的個數有 4 個,其中一堆大於 8 的情況我們可以選擇二進位制只有兩個 1 的,剩下兩堆各佔一個 1 即可,所以 sg[15] = 16。
當石子堆個數是 16 的時候,同理上述 8 的時候,所以 sg[16] = 15.
當石子堆個數是 17~22 的時候,要想分成三堆 sg[] 異或 = 自身,其中一堆的 sg[] 必須大於 15,如果分成三堆中有 15 的話,就必須有 8 或 16,顯然 15 是不能要的。這樣其中一堆就必須大於 16,你發現這時已經至少有兩個 1 了,所以並不能分成三堆 sg[] 異或 = 自身, sg[17~22] = 自身

後面也同樣可以通過上述推匯出來:
結論:
x%8 = 7:sg[x] = x+1;
x%8 = 0:sg[x] = x-1;
否則:sg[x] = x;

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6+5;
int a[N];
int main()
{
    int T, n;
    scanf("%d", &T);
    while(T--)
    {
        ll ans = 0
; scanf("%d", &n); for(int i = 0; i < n; i++) { scanf("%d", &a[i]); if(a[i]%8 == 0) ans ^= a[i]-1; else if(a[i]%8 == 7) ans ^= a[i]+1; else ans ^= a[i]; } if(ans) printf("First player wins.\n"); else printf("Second player wins.\n"); } return 0; }