CSP-CCF2020(第20次) 第五题 解密密码本 题解(AC自动机+字典树+DP)

题意:

给n个单词,要你构造长度为k的密文,使得密文解密后是由这n个单词构成的
解密书,有k页,一开始在第1页,每页26行。第i行一个字符a和一个数字b表示密文i解码后变成a,并翻到第b页
并且构造的密文中的子串不能出现给出的单词。
问k=1,2,3…m时候的方案数
n <= 50, 单词总长度 <= 50, m <= 2000
给出的单词不会出现重复,也不会有一个是另一个的前缀。单词可以重复在明文中出现。

解题思路:

看一下限制条件:

  1. 长度要为k
  2. 密文中不可以出现任何子串为某个单词
  3. 明文是由给出的单词构成
  4. 密码书翻到的页数不同,密文对应的明文不同

对于第二个限制,相当于建立一棵AC自动机之后,拿密文在AC自动机上跑,不会跑到某个后缀为给出的单词的结点上去。

对于第三个限制,相当于用解密出来的明文在字典树上跑,每次跑的都是字典树上存在的边,并且走到某个单词的结尾就跳回根,最终停留在根上。

看到单词总长度<=50,意味着建树之后结点数不会超过50,所以用:
d p ( i , j , k , l ) dp(i,j,k,l) dp(i,j,k,l)来表示当前长度为i,当前密文在ac自动机上走到结点j,明文在字典树上走到k,密码书翻到了第l页的方案数。
转移显然。利用滚动数组降低空间复杂度为 O ( n ∣ S ∣ 2 ) O(n|S|^2) O(nS2),时间复杂度为 O ( m n ∣ S ∣ 2 ) O(mn|S|^2) O(mnS2)
考试的时候忘记调用build获取fail指针的我是笨蛋。
赛后调用build之后本地测试25个测试用例均在1s内得到正确答案。

#include
using namespace std;
const int mod = 998244353;
const int maxn = 105;
int ch[maxn][26], ed[maxn], fail[maxn], nxt[maxn][26], val[maxn];
int n,m;
#define P pair
int g[55][26];//i th book got j,ned to full
int to[55][26];
char cmd[maxn];
int rt, tot;
void ins(char *s){
    int p = rt;
    while(*s){
        int x = *s - 'a';
        if(!ch[p][x]){
            ch[p][x] = ++tot;
            nxt[p][x] = tot;
        }
        p = ch[p][x];
        s++;
    }
    ed[p] = 1;
    val[p] = 1;
}
queue q;
void build(){
    while(q.size()) q.pop();
    fail[rt] = rt;
    for(int i = 0; i < 26; ++i){
        if(nxt[rt][i]){
            fail[ch[rt][i]] = rt;
            q.push(ch[rt][i]);
        }
    }
    while(q.size()){
        int u = q.front(); q.pop();
        for(int i = 0; i < 26; ++i){
            if(!nxt[u][i]){
                nxt[u][i] = nxt[fail[u]][i];
            }else{
                fail[nxt[u][i]] = nxt[fail[u]][i];
                ed[nxt[u][i]] |= ed[nxt[fail[u]][i]];
                q.push(nxt[u][i]);
            }
        }
    }
    return;
}
int dp[2][55][55][55];//len is i, ac is j, tree is k, book is l

int main(){

    scanf("%d%d", &n, &m);
    for(int i = 0; i < 26; ++i){
        for(int k = 1; k <= n; ++k){
            char s[5];
            int x = 0;
            scanf("%s",s);
            int ch = s[0] - 'a';
            int len = strlen(s);
            for(int j = 1; j < len; ++j) x = x*10 + s[j] - '0';
            g[k][i] = ch;
            to[k][i] = x;
        }
    }
    rt = tot = 0;
    while(scanf( "%s", cmd)!=EOF){
        ins(cmd);
        //cout<

你可能感兴趣的:(字典树)