[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;
}