bzoj 3895: 取石子

Description

Alice和Bob两个好朋含友又开始玩取石子了。游戏开始时,有N堆石子
排成一排,然后他们轮流操作(Alice先手),每次操作时从下面的规则中任选一个:
·从某堆石子中取走一个
·合并任意两堆石子
不能操作的人输。Alice想知道,她是否能有必胜策略。

Input

第一行输入T,表示数据组数。
对于每组测试数据,第一行读入N。
接下来N个正整数a1,a2…an,表示每堆石子的数量。

Output

对于每组测试数据,输出一行。
输出YES表示Alice有必胜策略,输出NO表示Alice没有必胜策略。

Sample Input

3
3
1 1 2
2
3 4
3
2 3 5
Sample Output

YES
NO
NO
HINT

 

100%的数据满足T<=100, N<=50. ai<=1000

 思路: 我们可以简单猜想当总操作数$\sum a[i] + n-1$ 是奇数的时候,先手必胜。 但事实并非如此,有些情况并不成立 。 我们可以发现,当n是1的时候,这样结论显然成立。  

另外,我们还可以总结出当没有数量为1的石子堆时,这个结论也是成立的。 下面我们先来证明这个结论。  

猜想: 当没有数量为1的堆,上面和是奇数的时候,先手必胜,如果是偶数,先手必败。 

1. 先证明奇数必胜,对于先手来说,如果n>1,那么只要选两堆合并, 那么总操作数变成偶数,n=1明显只能选择减少1操作,后手还是偶数。对于后手来说,无论他是减少1,还是合并操作,留下的总操作数一定还是奇数。 

对于某些读者来说,可能会问,如果后手把某个2变成1,先手该怎么办,其实这个很容易操作,如果堆数超过1,先手一定选择合并这个数量为1的堆,如果只有一堆了而且还是1,明显先手必胜了。 所以,先手总是有办法让

后手必败(操作数为偶数的局面),后手无论怎么走,都会让先手必胜(变成操作为奇数的局面),所以我们证明成立。 

2. 在上面,我们也证明了当操作数是偶数的时候,先手是必败的。 

 

接下来我们考虑有数量为1的堆的时候的情况,这个情况比较复杂,因为如果某个人把1减少1,那么这个堆同时也消失了,相当于操作数减少了2。 

由于数据规模不是很大,我们采用了动态规划的思想,用记忆化搜索来实现情况。  

$f[a][b]$表示当数量为1的堆有a个,剩下的堆的操作数是b的时候,先手是必胜还是必败,$f[a][b]=1$表示必胜,否则必败。b中不会出现数量为1的堆,除非只剩下一个堆了。   

情况1: 当a>=2的时候,合并两个数量为1的堆,这样就让b这边的操作数增加了2+(b>0),因为如果原来b大于0,相当于操作数增加了3,否则b原来是0,那么操作是只增加2. 

情况2: 和a>0 并且b>0 的时候,我们可以合并一个数量为1的堆和b中一个数量不为1的堆,那么a减少1,b增加1

情况3:b>=2,我们合并b里面的两个堆或者减少1,无论哪种,都是让b里面的操作数减少1。 

情况4: 当a>0的时候,我们可以减少一个数量为1的堆,这样a就减少1,b不变。  

 

另外有一种非常特殊的情况,就是b等于1了,刚才我们说了,b里面不会出现数量为1的堆,除非只剩下一个堆了。因为b里面只要堆的数量超过1,就一定可以用合并超过替代减少1操作,这样是等价的。 

除非b里面只有一个堆了,那么我们就只能不断减少1了。   

所以当b是1的时候,实际我们求的问题应该变成f[a+1][0] 

 

以上内容是我自己的一个总结,网上有些题解感觉写的不是很清楚,我自己理了一下思路,重新写了一个题解。 

 

 1 #include<bits/stdc++.h>
 2 using namespace std;  
 3 int const N=50+3;  
 4 int f[N][60000],n,a[N];  
 5 int dfs(int a,int b){
 6     if(b==1) return f[a][b]=dfs(a+1,0);  
 7     if(f[a][b]>-1) return f[a][b];  
 8     if(!a) return f[a][b]=b&1;  
 9     int t=2;  
10     if(a>=2) t=min(t,dfs(a-2,b+2+(b>0)));  
11     if(a && b) t=min(t,dfs(a-1,b+1));  
12     if(b) t=min(t,dfs(a,b-1));  
13     if(a) t=min(t,dfs(a-1,b));  
14     return f[a][b]=t^1;    
15 }  
16      
17 int main(){
18     int cas;  
19     scanf("%d",&cas);  
20     memset(f,-1,sizeof(f));   
21     while (cas--){
22         scanf("%d",&n);  
23         int x=0,y=0,num=0;  
24         for(int i=1;i<=n;i++){
25             scanf("%d",&a[i]);   
26             if(a[i]==1) x++; 
27             else num++,y+=a[i]+1;  
28         }  
29         if(num) y--;     
30         if(dfs(x,y)) puts("YES");  
31         else puts("NO");  
32     }
33     return 0;  
34 } 
View Code

 

posted @ 2019-06-22 13:25  zjxxcn  阅读(485)  评论(0编辑  收藏  举报