传送门:活动 - AcWing
思路:
结论:在所有堆的石子个数>1的情况下
只要石子数+石子堆数-1==b是奇数,那么先手必胜。b是不计算所有个数为1的石子堆得出的。
b是奇数的情况下一定存在一个偶数后继,是偶数的情况下所有的后继都一定是奇数后继。
定义终局局面:只有一堆并且石子的个数是奇数,这样子到最后先手留给后手的是一个偶数石子个数局面,此时后手必败。
在存在石子个数为1的堆的情况下:
定义一个二元状态f(a,b),a表示当前个数为1的石子堆的数量,b表示个数不为1的所有石子堆的个数加上石子的堆d数减1的值。
现在有一个f(a,b)状态,在这两部分当中有五种操作:
1.从a中取一个,变成f(a-1,b)
2.从b中取一个,变成f(a,b-1)
3.合并b中的2个,变成f(a,b-1)
4.合并a中的2个,变成f(a-2,b+3)
5.合并a中的1个和b中的一个,变成f(a-1,b+1)
代码:
#include
#include
#include
#include
using namespace std;
const int N=60,M=5e4+10;
typedef long long ll;
int n,m,k;
int f[N][M];
int dp(int a,int b)
{
if(f[a][b]!=-1) return f[a][b];
if(!a) return f[a][b]=b%2;
if(b==1) return dp(a+1,0);
if(a&&!dp(a-1,b)) return f[a][b]=1;
if(b&&!dp(a,b-1)) return f[a][b]=1;
if(a>=2&&!dp(a-2,b+(b?3:2))) return f[a][b]=1;
if(a&&b&&!dp(a-1,b+1)) return f[a][b]=1;
return f[a][b]=0;
}
int main()
{
int t;
scanf("%d",&t);
memset(f,-1,sizeof f);
while(t--)
{
int n;
scanf("%d",&n);
int a=0,b=0;
for(int i=0;i
传送门:取石子游戏
思路:
设left[i][j]为在左边放多少个石子,先手必败,设right[i][j]为在右边放多少个石子,先手必败。
对于left[i][j]是一定存在且唯一的。
证明,假如现在存在两个数l1和l2且l1>l2,使得左边分别放l1,l2都是先手必败的话,如果先手取得使l1变成l2,那么留给后手的就业是一个必败态,但这是不可能的,必败态无法转移到另一个必败态,所以证明必败态唯一。
设有上图一个[i,j]的区间,X为当前位置j的取值,L为left[i][j-1]的值,R为right[i][j-1]的值。
对于i-1的取值有如下几种情况:
1. 若R==X,此时left[i][j]=0;
2.若X 3.若L>R,R 若L 4.若X>L且X>R,left[i][j],left[i][j]=X , 不管先手怎么取,后手都在另一边做相同操作,当某一次先手取完后满足上面三种情况之一后手就按照上述的情况来取,这样子先手也必败。 同时,上面的这些在右边也是个对称的操作。 代码:#include