1. 程式人生 > >BZOJ1188 [HNOI2007]分裂遊戲

BZOJ1188 [HNOI2007]分裂遊戲

des pre 代碼 文件 空格 要求 ons 希望 但是

Description

聰聰和睿睿最近迷上了一款叫做分裂的遊戲。 該遊戲的規則試: 共有 n 個瓶子, 標號為 0,1,2.....n-1, 第 i 個瓶子中裝有 p[i]顆巧克力豆,兩個人輪流取豆子,每一輪每人選擇 3 個瓶子。標號為 i,j,k, 並要保證 i < j , j < = k 且第 i 個瓶子中至少要有 1 顆巧克力豆,隨後這個人從第 i 個瓶子中拿走一顆豆 子並在 j,k 中各放入一粒豆子(j 可能等於 k) 。如果輪到某人而他無法按規則取豆子,那麽他將輸 掉比賽。勝利者可以拿走所有的巧克力豆! 兩人最後決定由聰聰先取豆子,為了能夠得到最終的巧克力豆,聰聰自然希望贏得比賽。他思考 了一下,發現在有的情況下,先拿的人一定有辦法取勝,但是他不知道對於其他情況是否有必勝 策略,更不知道第一步該如何取。他決定偷偷請教聰明的你,希望你能告訴他,在給定每個瓶子 中的最初豆子數後是否能讓自己得到所有巧克力豆,他還希望你告訴他第一步該如何取,並且為 了必勝,第一步有多少種取法? 假定 1 < n < = 21,p[i] < = 10000

Input

輸入文件第一行是一個整數t表示測試數據的組數,接下來為t組測試數據(t<=10)。每組測試數據的第一行是瓶子的個數n,接下來的一行有n個由空格隔開的非負整數,表示每個瓶子中的豆子數。

Output

對於每組測試數據,輸出包括兩行,第一行為用一個空格兩兩隔開的三個整數,表示要想贏得遊戲,第一步應該選取的3個瓶子的編號i,j,k,如果有多組符合要求的解,那麽輸出字典序最小的一組。如果無論如何都無法贏得遊戲,那麽輸出用一個空格兩兩隔開的三個-1。第二行表示要想確保贏得比賽,第一步有多少種不同的取法。

Sample Input

2
4
1 0 1 5000
3
0 0 1

Sample Output

0 2 3
1
-1 -1 -1
0

題解

我們把每個石子看做一個單獨的遊戲(因為它們是獨立的),那麽其SG值只取決於它到最右邊的距離。

設其$SG$值為$SG_i$,其中$i$表示石子到右邊界的位置,那麽$SG_i = mex\left\{SG_j xor SG_k \mid 0 \leq j, k < i\right\}

由於$1 \leq n < 21$,我們可以暴力$O(n^3)$計算所有$SG$值。找到方案及統計方案數時$O(n^3)$枚舉第一步的走法即可。

代碼:

#include <cstdio>
const int N = 25;
int SG[N], a[N];
int f[10000];
int main() {
  int T, n, sg = 0;
  SG[0] = 0;
  for (int i = 1; i < N; ++i) {
    for (int j = 0; j < i; ++j)
      for (int k = 0; k < i; ++k)
        f[SG[j] ^ SG[k]] = i;
    for (SG[i] = 0; f[SG[i]] == i; ++SG[i]);
  }
  scanf("%d", &T);
  while (T--) {
    scanf("%d", &n);
    sg = 0;
    for (int i = n - 1; ~i; --i) {
      scanf("%d", &a[i]);
      if (a[i] & 1) sg ^= SG[i];
    }
    if (!sg) {
      printf("-1 -1 -1\n0\n");
      continue;
    }
    int ans = 0;
    for (int i = n - 1; ~i; --i)
      for (int j = i - 1; ~j; --j)
        for (int k = j; ~k; --k)
          if (a[i] && !(sg ^ SG[i] ^ SG[k] ^ SG[j]) && !(ans++))
            printf("%d %d %d\n", n - i - 1, n - j - 1, n - k - 1);
    printf("%d\n", ans);
  }
  return 0;
}

  

BZOJ1188 [HNOI2007]分裂遊戲