题意
给出一个字典,每个单词去掉元音字母 A、E、I、O、U
之后形成一个新字典。
先给出一个只有辅音组成的串,用原字典中的单词还原该串,若存在多种还原方式,输出还原后元音字母数量最多的那种,若依旧多种,则任意输出。
思路
ac自动机fail树上跑dp的一眼套路题。
总结一下遇到的坑:
- 多个单词去掉元音字母之后形成的新单词相同。
- 若原单词只有辅音字母。
- dp过程中未保证完全还原辅音串。
- 特殊样例将 跳fail的过程卡成了 \(n^2\) 。
解决办法:
- 在字典树的结尾节点保存编号时,保存最大值。
- 将初值从 $0 $ 改为 \(-1\) 。
- dp数组初值同样改为 \(-1\),\(dp[0] = 0\) ,dp只能由非\(-1\)的状态转移。
- 对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 */