1. 程式人生 > >【NOI2015模擬9.9】取石子(博弈)

【NOI2015模擬9.9】取石子(博弈)

這裡寫圖片描述

The Solution

個人覺得這種題要不就切掉要不就爆0了,所以我們要大膽地猜結論,然後去證明。(這也是一種思路吧)

因為一次只能取走一顆石子,因此對於所有石子,我們能進行的操作總數就是 s = 石子總數 + 石子堆數 - 1 .

我們可以感性地先猜一猜,如果考慮最簡單的情況一堆的話,那麼如果s是奇數那麼很顯然先手必勝,若是偶數那麼先手必敗。

那麼我們拓寬一下思路,拓廣到n堆石子。

首先數量為1的石子堆單獨討論,因此我們在統計時候不記錄數量為1的石子堆對答案的貢獻.

看到到資料很小,先考慮記憶化暴力。

我們設f[i][j]表示有i堆石子數目為1,能進行的運算元為j.
f[i][j]為1表示先手必勝,0表示先手必敗.
這樣的狀態設計可以讓我們把數量為1的石子和其他石子分開單獨討論. 這樣我們就可以把數量為1的那些暴力出來.

然後在不考慮大小為1的石子堆的情況下,如果能夠進行的運算元為奇數,則先手必勝,否則後手必勝.

證明如下:
如果只有1堆石子,該結論顯然成立。
如果有多堆石子,每堆石子個數都大於1,並且s為偶數,下面我們證明這樣先手必敗。
1.如果先手選擇合併兩堆石子,那麼每堆石子的個數依然大於1,s變為奇數。
2.如果先手選擇從一堆石子數大於2的堆中拿走一枚石子,那麼同上每堆石子個數依然大於1,s變為奇數。
3.如果先手選擇從一堆石子數等於2的堆中拿走一枚石子,那麼後手可以合併剩下的1枚石子到任意一個堆。
那樣s的奇偶性不變,每堆石子的個數依然大於1.
綜上所述,結論成立。
暴力時我們這樣操作:
然後可進行的操作有以下幾種:
1.取走一個大小為1的石子堆的石子.
2.取走某個大小不為1的石子堆的石子或者合併兩個大小不為1的石子堆.
2.將一個大小為1的石子合併到另一個大小不為1的石子堆中.
3.合併兩個大小為1的石子堆的石子.

這次題解我難得寫那麼長哎(〃'▽'〃)
~~或許是第一次做博弈切了比較有興趣~~

CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define N 60
#define M (50 * 1000 + 5)

using namespace std;
int n,f[N][M];
bool Mark[N][M];

int
read(int &n) { char ch =' '; int q = 0,w = 1; for (;(ch != '-') && (ch < '0' || ch > '9');ch = getchar()); if (ch == '-') w = -1,ch = getchar(); for (;(ch >= '0' && ch <= '9');ch = getchar()) q = q * 10 + ch - 48; n = q * w; return n; } int Dfs(int x,int y) { if (x == 0) return (y & 1); if (y == 1) return Dfs(x + 1,0); if (Mark[x][y]) return f[x][y]; Mark[x][y] = true; if (x && ! Dfs(x - 1,y)) return (f[x][y] = 1); if (x && y && ! Dfs(x - 1,y + 1)) return (f[x][y] = 1); if (x >= 2 && ! Dfs(x - 2, y + 2 + (y ? 1 : 0))) return (f[x][y] = 1); if (y && ! Dfs(x,y - 1)) return (f[x][y] = 1); return (f[x][y] = 0); } int main() { int T,tot; read(T); memset(f,-1,sizeof(f)); while (T --) { read(n); int x, one = 0,many = 0; fo(i,1,n) { read(x); if (x == 1) one ++; else many += x + 1; } many --; many = many > 0 ? many : 0; printf(Dfs(one,many) ? "YES\n" : "NO\n"); } return 0; }