题面
题意
从左到右有几堆石子,双方轮流取石子,每次取时只能从最左边或者是最右边的堆中取任意个石子,不能操作的人算输,问先手有无必胜策略。
做法
首先定义dp状态:
l
e
f
t
[
i
]
[
j
]
left[i][j]
left[i][j]表示在第
i
i
i堆石子到第
j
j
j堆石子左边放
l
e
f
t
[
i
]
[
j
]
left[i][j]
left[i][j]个石子后是必败态。
r
i
g
h
t
[
i
]
[
j
]
right[i][j]
right[i][j]表示在第
i
i
i堆石子到第
j
j
j堆石子左边放
r
i
g
h
t
[
i
]
[
j
]
right[i][j]
right[i][j]个石子后是必败态。
两个值都可以为0。
不难发现这两个值存在且唯一,最后只要判断
l
e
f
t
[
2
]
[
n
]
left[2][n]
left[2][n]是否等于
n
u
m
[
1
]
num[1]
num[1]即可。
下面考虑
l
e
f
t
[
i
]
[
j
]
left[i][j]
left[i][j]的状态转移(
r
i
g
h
t
[
i
]
[
j
]
right[i][j]
right[i][j]与它的转移方式相同):
递归边界:当
i
=
j
i=j
i=j时,很显然,
l
e
f
t
[
i
]
[
j
]
=
r
i
g
h
t
[
i
]
[
j
]
=
n
u
m
[
i
]
left[i][j]=right[i][j]=num[i]
left[i][j]=right[i][j]=num[i]
首先求出
L
=
l
e
f
t
[
i
]
[
j
−
1
]
,
R
=
r
i
g
h
t
[
i
]
[
j
−
1
]
L=left[i][j-1],R=right[i][j-1]
L=left[i][j−1],R=right[i][j−1]
若
R
=
n
u
m
[
v
]
R=num[v]
R=num[v],则
l
e
f
t
[
i
]
[
j
]
=
0
left[i][j]=0
left[i][j]=0
然后可以发现,后手可以尽量复制先手的操作,先手在左边取
x
x
x个后,后手可以在右边也同样取
x
x
x个,这样可以如果
l
e
f
t
[
i
]
[
j
]
=
n
u
m
[
j
]
left[i][j]=num[j]
left[i][j]=num[j],就可以保证先手先取完某一堆,这样只要保证先手取完某一堆后不是必败态就行,也就说如果先手取完第
i
i
i堆,则要保证此时第
j
j
j堆剩余的石子数不等于R,如果先手取完第
j
j
j堆,则要保证此时第
i
i
i堆剩余的石子数不等于L,因此还要加几个判断:
1.若
R
<
n
u
m
[
v
]
<
L
R<num[v]<L
R<num[v]<L,则当先手在第
i
i
i堆取了
n
u
m
[
v
]
−
R
num[v]-R
num[v]−R个时,后手不能在第
j
j
j堆取
n
u
m
[
v
]
−
R
num[v]-R
num[v]−R个(否则先手可以直接取完第
i
i
i堆的所有石子),因此
l
e
f
t
[
i
]
[
j
]
=
n
u
m
[
v
]
−
1
left[i][j]=num[v]-1
left[i][j]=num[v]−1,这样当先手在左边取了
n
u
m
[
v
]
−
R
num[v]-R
num[v]−R个或更多个石子后,后手只要在右边取
n
u
m
[
v
]
−
R
+
1
num[v]-R+1
num[v]−R+1个石子即可。
2.若
L
<
n
u
m
[
v
]
<
R
L<num[v]<R
L<num[v]<R,则当先手在第
j
j
j堆取了
n
u
m
[
v
]
−
L
num[v]-L
num[v]−L个时,后手同样不能复制先手的操作,因此
l
e
f
t
[
i
]
[
j
]
=
n
u
m
[
v
]
+
1
left[i][j]=num[v]+1
left[i][j]=num[v]+1,这样当先手在右边取了
n
u
m
[
v
]
−
L
num[v]-L
num[v]−L个或更多个石子后,后手只要在左边取
n
u
m
[
v
]
−
R
+
1
num[v]-R+1
num[v]−R+1个石子即可。
3.若
n
u
m
[
v
]
>
max
(
L
,
R
)
num[v]>\max(L,R)
num[v]>max(L,R),则
l
e
f
t
[
i
]
[
j
]
left[i][j]
left[i][j]仍为
n
u
m
[
v
]
num[v]
num[v],理由与上面相同。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1010
using namespace std;
int T,n,num[N],le[N][N],ri[N][N];
int askl(int u,int v);
int askr(int u,int v)
{
if(u==v) return num[u];
if(ri[u][v]!=-1) return ri[u][v];
int L,R,res;
L=askl(u+1,v);
R=askr(u+1,v);
if(num[u]==L) res=0;
else res=num[u]-(num[u]>L)+(num[u]>=R);
return ri[u][v]=res;
}
int askl(int u,int v)
{
if(u==v) return num[u];
if(le[u][v]!=-1) return le[u][v];
int L,R,res;
L=askl(u,v-1);
R=askr(u,v-1);
if(num[v]==R) res=0;
else res=num[v]-(num[v]>R)+(num[v]>=L);
return le[u][v]=res;
}
int main()
{
int i,j;
cin>>T;
while(T--)
{
memset(le,-1,sizeof(le));
memset(ri,-1,sizeof(ri));
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&num[i]);
}
printf("%d\n",num[1]!=askl(2,n));
}
}