[ABC215E]Chain Contestant
壹、题目描述 ¶
贰、题解 ¶
◆ 前言
我的思维什么时候到了那么孱弱的地步,连一道 \(500\) 的纯 \(\rm DP\) 都已经不会做了。
◆ 观察特殊点
显然是 \(\rm DP\),那么关键在于找准子状态,以及转移。
题目要求我们选出的子序列中,相同的字符必须挨在一起,那么,我们应该注意到,某个状态有这样一些有效信息:
- 当前是哪一位;
- 我们已经使用过的字符;
- 最后填的字符是什么;
我们考虑通过记录这些信息进行 \(\rm DP\).
◆ 设计状态
一个比较节省的想法是,定义 \(f(i,S)\) 表示以第 \(i\) 位的字符结尾,选择字符集为 \(S\) 的方案数,这样看似直接解决了 当前是哪一位 以及 最后填的字符是什么 两个问题,但是,当考虑到转移时,处理中间有一段字符没有选择的时候,我们要去枚举上一个选择字符的位置,再加上枚举 \(S\),复杂度将会达到 \(\mathcal O(n^22^\Sigma)\),这是我们无法接受的。
但是,最后填的字符种类只有 \(10\),我们不妨将这两个分开,定义状态 \(f(i,S,c)\) 表示考虑到第 \(i\) 位,选择字符集为 \(S\),最后一个填的字符为 \(c\) 的方案数,转移考虑枚举 \(S,c(c\in S)\),并且分为几种情况:
- 当我们选择第 \(i\) 位的字符时,设第 \(i\) 位字符为 \(x\),那么:
-
- 如果 \(c=x\),那么 \(\forall S,x=c\;\land x\in S,f(i,S,x)\leftarrow f(i,S,x)+f(i-1,S,x)\),在前面所有这种情况下的字符串后面再接上一个 \(x\);
- 如果 \(c\neq x\),那么 \(\forall S,x\notin S\;\land c\in S,f(i,S\cup\{x\},x)\leftarrow f(i,S\cup\{x\},x)+f(i-1,S,c)\),如果上一次选择的字符和当前位不一样,那么转移来的字符串中一定不能含有 \(x\),不然它们就分开了;
- 当只选择了这一位时,\(f(i,\{x\},x)\leftarrow f(i,\{x\},x)+1\);
- 到我们不选择第 \(i\) 位字符时,那么 \(\forall S,c\in S,f(i,S,c)\leftarrow f(i,S,c)+f(i-1,S,c)\),这是处理中间间隔字符没有选择的情况;
答案统计,枚举合法的 \((S,c)\),将 \(f(n,S,c)\) 加起来即可。复杂度 \(\mathcal O(2^\Sigma\times n\times \Sigma)\).
叁、参考代码 ¶
#include <bits/stdc++.h>
using namespace std;
// #define NDEBUG
#include <cassert>
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
#define mmcpy(a, b) memcpy(a, b, sizeof a)
// #define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int mod=998244353;
const int maxn=1000;
const int sigma=10;
int f[maxn+5][1<<sigma][sigma];
char s[maxn+5]; int n;
inline void input(){
n=readin(1);
scanf("%s", s+1);
}
inline void getf(){
int x=s[1]-'A', U=1<<sigma;
f[1][1<<x][x]=1;
for(int i=2; i<=n; ++i){
x=s[i]-'A';
for(int s=0; s<U; ++s) for(int c=0; c<sigma; ++c) if(s>>c&1){
f[i][s][c]=(f[i][s][c]+f[i-1][s][c]*(c==x? 2: 1)%mod)%mod;
if(!(s>>x&1)) f[i][s^(1<<x)][x]=(f[i][s^(1<<x)][x]+f[i-1][s][c])%mod;
}
f[i][1<<x][x]=(f[i][1<<x][x]+1)%mod;
}
int ans=0;
for(int s=0; s<U; ++s) for(int c=0; c<sigma; ++c)
if(s>>c&1) ans=(ans+f[n][s][c])%mod;
writc(ans);
}
signed main(){
input();
getf();
return 0;
}
肆、关键之处 ¶
先分析好哪些信息是有用的,再考虑如何设计状态将这些信息包含进去,并且要方便转移。