Kattis - nvwls (AC自动机last优化 + dp)

题意

给出一个字典,每个单词去掉元音字母 A、E、I、O、U 之后形成一个新字典。

先给出一个只有辅音组成的串,用原字典中的单词还原该串,若存在多种还原方式,输出还原后元音字母数量最多的那种,若依旧多种,则任意输出。

传送门

思路

ac自动机fail树上跑dp的一眼套路题。

总结一下遇到的坑:

  1. 多个单词去掉元音字母之后形成的新单词相同。
  2. 若原单词只有辅音字母。
  3. dp过程中未保证完全还原辅音串。
  4. 特殊样例将 跳fail的过程卡成了 \(n^2\)

解决办法:

  1. 在字典树的结尾节点保存编号时,保存最大值。
  2. 将初值从 $0 $ 改为 \(-1\)
  3. dp数组初值同样改为 \(-1\)\(dp[0] = 0\) ,dp只能由非\(-1\)的状态转移。
  4. 对fail数组进行 last优化 last[u] = len[fail[u]]? fail[u]: last[fail[u]];

last优化

普通方法将建图+匹配的复杂度成功优化为了 𝑂(∑𝑛+𝑚) ,但是,匹配成功时的计数也是需要跳fail边的。然而,为了跳到一个结束节点,我们可能需要中途跳到很多没用的伪结束节点:

如果一个节点的fail指向一个结尾节点,那么这个点也成为一个(伪)结尾节点。在匹配时,如果遇到结尾节点,就进行相应的计数处理。

这里面就又有优化的余地了:对于不是真正结束节点的伪结束点,直接跳过它就好了。我们用一个last指针表示“在它顶上的fail边所指向的一串节点中,第一个真正的结束节点”。于是,每次计数处理时,我们不跳fail边,改为跳last边,省去了很多冗余操作。

获得last指针的方法也十分简单,就是在void build()中加一句话:

last[u] = len[fail[u]]? fail[u]: last[fail[u]];

Code

#include <bits/stdc++.h>

using namespace std;

const int maxn = 3e5+10;

char a[maxn], str[maxn], S[maxn], tmp[maxn];
int res[maxn], pos[maxn], L[maxn];
int n, pn;
int val[maxn];

int trie[maxn][26], fail[maxn], last[maxn];
int len[maxn], dep[maxn];
int e[maxn];
int que[maxn],h, t;
int tot;

inline void insert(string t, int id) {
    register int p = 0;
    for (register int c, i = 0; t[i]; ++i) {
        c = t[i]-'A';
        if(!trie[p][c]) {
            trie[p][c] = ++tot;
            dep[tot] = dep[p] + 1;
        }
        p = trie[p][c];
    }
    if(val[e[p]] <= val[id]) e[p] = id;
    len[p] = dep[p];
}

inline void build() {
    h = 1, t = 0;
    for (register int i = 0; i < 26; ++i) {
        if(trie[0][i])
            que[++t] = trie[0][i];
    }
    while(h <= t) {
        register int u = que[h++];
        for (register int i = 0; i < 26; ++i) {
            if(trie[u][i]) fail[trie[u][i]] = trie[fail[u]][i], que[++t] = trie[u][i];
            else trie[u][i] = trie[fail[u]][i];
        }
        last[u] = len[fail[u]]? fail[u]: last[fail[u]];
    }
}

int pre[maxn], dp[maxn], sta[maxn];

inline void count(char* str) {
    dp[0] = 0;
    register int p = 0, LL = 0;

    for (register int i = 1; str[i]; ++i, ++LL) {
        register int c = str[i]-'A';
        p = trie[p][c];
        dp[i] = -1;
        for (register int j = p; j; j = last[j]) {
            if(e[j] && dp[i-len[j]]!=-1 && dp[i] < dp[i-len[j]]+val[e[j]]) {
                dp[i] = dp[i-len[j]]+val[e[j]];
                sta[i] = e[j];
                pre[i] = i-len[j];
            }
        }
    }

    for (register int i = LL; i > 0; i = pre[i]) res[++pn] = sta[i];
    for (register int i = pn; i >= 1; --i) {
        for (register int j = pos[res[i]]; j < pos[res[i]] + L[res[i]]; ++j) putchar(S[j]);
        if(i==1) putchar('\n');
        else putchar(' ');
    }
}

int main() {
    val[0] = -1;
    scanf("%d", &n);
    register  int ll = 0;
    for (register int i = 1; i <= n; ++i) {
    scanf("%s", a);
        pos[i] = ll; L[i] = strlen(a);
        strcat(S+ll, a); ll += L[i];
        val[i] = 0;
        register int LLL = 0;
        for (register int j = 0; a[j]; ++j) {
            char ch = a[j];
            if(ch=='A'||ch=='E'||ch=='I'||ch=='O'||ch=='U') ++val[i];
            else tmp[LLL++] = a[j];
        }
        tmp[LLL] = '\0';
        insert(tmp, i);
    }
    build();
    scanf("%s" ,str+1);
    count(str);
    return 0;
}
/*
8
BAEI
BAEIOU
CAEIOU
BCAEIOU
BCDAEIOU
DAEIOU
BDAEIOU
CDAEIOU
BCD
*/
posted @ 2019-10-14 11:58  Acerkoo  阅读(493)  评论(0编辑  收藏  举报