[ABC215E]Chain Contestant

壹、题目描述 ¶

传送门 to Atcoder.

贰、题解 ¶

◆ 前言

我的思维什么时候到了那么孱弱的地步,连一道 \(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;
}

肆、关键之处 ¶

先分析好哪些信息是有用的,再考虑如何设计状态将这些信息包含进去,并且要方便转移。

posted @ 2021-08-23 16:48  Arextre  阅读(106)  评论(0编辑  收藏  举报