[COCI2021-2022#1] Kamenčići 解题记录
题意简述
一个长度为
N
N
N 的字符串
S
S
S,仅由 C
和 P
组成。轮流每次从两端取出一个字符,先取出
K
K
K 个 C
的失败,求先手必胜还是必败。
题目分析
考虑区间 DP,设
d
p
l
,
r
,
k
dp_{l,r,k}
dpl,r,k 表示取到还剩区间
[
l
,
r
]
[l,r]
[l,r],当前轮到的人已经取了
k
k
k 个 C
,当前状态是必胜还是必败。
因为只剩一个石子的状态是确定的,所以区间从少往多转移,即区间
[
l
,
r
]
[l,r]
[l,r] 从区间
[
l
+
1
,
r
]
[l+1,r]
[l+1,r] 和
[
l
,
r
−
1
]
[l,r-1]
[l,r−1] 转移。
下一个取得的数量
t
t
t 怎么求?
假设我们取到了
[
l
,
r
]
[l,r]
[l,r],并且当前有
k
k
k 个 C
被取了,那么
t
t
t 就是区间
[
l
−
1
,
r
+
1
]
[l-1,r+1]
[l−1,r+1] 的 C
的数量减去现在的
k
k
k,用前缀和维护。
由于相邻的两个状态相反,所以转移的时候需要取反。
状态转移方程:
d
p
l
,
r
,
k
=
¬
d
p
l
+
1
,
r
,
t
∨
¬
d
p
l
,
r
−
1
,
t
dp_{l,r,k}=\lnot \ dp_{l+1,r,t} \vee \lnot \ dp_{l,r-1,t}
dpl,r,k=¬ dpl+1,r,t∨¬ dpl,r−1,t
AC Code
#include<bits/stdc++.h>
#define arrout(a,n) rep(i,1,n)std::cout<<a[i]<<" "
#define arrin(a,n) rep(i,1,n)std::cin>>a[i]
#define rep(i,x,n) for(int i=x;i<=n;i++)
#define dep(i,x,n) for(int i=x;i>=n;i--)
#define erg(i,x) for(int i=head[x];i;i=e[i].nex)
#define dbg(x) std::cout<<#x<<":"<<x<<" "
#define mem(a,x) memset(a,x,sizeof a)
#define all(x) x.begin(),x.end()
#define arrall(a,n) a+1,a+1+n
#define PII std::pair<int,int>
#define m_p std::make_pair
#define u_b upper_bound
#define l_b lower_bound
#define p_b push_back
#define CD const double
#define CI const int
#define int long long
#define il inline
#define ss second
#define ff first
#define itn int
CI N=355;
int32_t n,K,dp[N][N][N/2],sum1[N],sum2[N];
std::string s;
int dfs(int l,int r,int k){
if(dp[l][r][k]!=-1){
return dp[l][r][k];
}
if(k>=K){
return 0;
}
int t=sum1[l-1]+sum2[r+1]-k;
if(t>=K){
return 1;
}
int ans=(!dfs(l+1,r,t)|!dfs(l,r-1,t));
return dp[l][r][k]=ans;
}
signed main() {
std::cin>>n>>K>>s;
s=" "+s;
mem(dp,-1);
rep(i,1,n){
sum1[i]=sum1[i-1]+(s[i]=='C');
}
dep(i,n,1){
sum2[i]=sum2[i+1]+(s[i]=='C');
}
if(dfs(1,n,0)){
puts("DA");
}else{
puts("NE");
}
return 0;
}