【題解】#10246. 「一本通 6.7 練習 3」取石子

Description

Sample Input

3c++

3spa

1 1 2code

2blog

3 4ip

3input

2 3 5it

Sample Output

YESio

NOclass

NOim

Solution

我天,真神仙題!

這題實際上是博弈論DP,一開始還想着直接用SG * 過去。

咱們先從最簡單的入手:

只有一堆石子時咱們能夠不考慮合併形成的影響那麼一我的贏的狀況只有多是他剩下能夠進行的操做數是奇數。

(這裏咱們發現剩下能夠進行的操做數只有取一個石子)

那若是有兩個堆。

(假設只有一個石子的堆叫作寂寞堆,大於一個石子的堆叫作熱鬧堆)

那麼咱們要分兩種狀況分別討論:

\(1.\) 咱們有兩個堆,一個寂寞堆一個熱鬧堆。

那麼咱們假設寂寞堆 \(1\) 個石子,熱鬧堆 \(2\) 個石子,那麼很明顯咱們當前只有要麼從兩個堆裏取一個,要麼合併。

  • 首先考慮合併:合併以後熱鬧堆的奇偶性變了,同時合併以後取的是對手,這樣就保證了對手贏。

  • 考慮先把寂寞堆取完,那麼咱們在熱鬧堆中是能夠直接根據奇偶求出誰會贏。若是先取熱鬧堆對手是有贏的策略的。

到這裏咱們發現好像寂寞堆會影響答案,若是隻有熱鬧堆,熱鬧堆之間的合併不會改變他們的奇偶,對咱們考慮沒有影響,只會致使贏輸的人不同。

但若是出現了寂寞堆,寂寞堆的合併會影響熱鬧堆的奇偶性,因此要特殊考慮寂寞堆。

這個時候就要咱們上博弈論DP了 然而我不知道爲何要上(逃

設狀態 \(f[i][j]\) 表示有 \(i\) 個寂寞堆,\(j\) 次對於熱鬧堆的操做時當前操做的人是贏仍是輸。

這個狀態好詭異

咱們轉移怎麼辦呢?

分類討論一下:

\(1.\) 寂寞堆操做

  • 寂寞堆取一個石子,很明顯轉移到 \(f[i-1][j]\)
  • 寂寞堆合併(2個寂寞堆) 轉移到 \(f[i-2][j+2]\)(合併以後多了一個熱鬧堆,要對熱鬧堆進行兩次取石子操做),但忽然發現若是還有熱鬧堆的話咱們還會多一次合併操做,那就轉移到 \(f[i-2][j+2+(j?1:0)]\)
  • 寂寞堆合併到熱鬧堆上,轉移到 \(f[i-1][j+1]\)

\(2.\) 熱鬧堆操做

  • 從熱鬧堆裏取一個石子,須要考慮是否取了以後變爲寂寞堆。也就是 \(j\) 是否爲 \(1\)。兩個轉移 \(f[i+1][0](j==1),f[i+1][j-1]\)

這樣以後好像就沒啥子了。注意一下細節就莫得了。

#include<bits/stdc++.h>
using namespace std;

int T,n;
const int N=55,M=1005;
int a[N],f[N][M*N];

inline int dfs(int num,int sum){
	if(num<=0 && sum<=0) return 0;
	if(f[num][sum]!=-1) return f[num][sum];
	if(num<=0) return f[num][sum]=(sum&1);
	if(sum==1) return f[num][sum]=dfs(num+1,0);
	f[num][sum]=0;
	if(num && !dfs(num-1,sum)) return f[num][sum]=1;					// 拿一個寂寞堆的石子
	if(sum && !dfs(num,sum-1)) return f[num][sum]=1;    				// 把一個熱鬧堆裏拿掉一個石子
	if(num && sum && !dfs(num-1,sum+1)) return f[num][sum]=1;			// 把一個寂寞堆合併到熱鬧堆上
	if(num>1 && !dfs(num-2,sum+2+(sum?1:0))) return f[num][sum]=1;		// 把兩個寂寞堆合併
	return f[num][sum];
}

int main(){
	scanf("%d",&T);
	memset(f,-1,sizeof(f));
	while(T--){
		int cnt=0,step=0;
		scanf("%d",&n);
		for(int i=1;i<=n;++i){
			scanf("%d",&a[i]);
			if(a[i]==1) cnt++;
			if(a[i]>1)  step+=a[i]+1;
		}
		if(step) step--;
		printf("%s\n",dfs(cnt,step)?"YES":"NO");	
	}
	return 0;
}
相關文章
相關標籤/搜索