题目:https://ac.nowcoder.com/acm/contest/147/F
Niuniu is practicing typing.
Given n words, Niuniu want to input one of these. He wants to input (at the end) as few characters (without backspace) as possible,
to make at least one of the n words appears (as a suffix) in the text.
Given an operation sequence, Niuniu want to know the answer after every operation.
An operation might input a character or delete the last character.
输入描述:
The first line contains one integer n.
In the following n lines, each line contains a word.
The last line contains the operation sequence.
‘-’ means backspace, and will delete the last character he typed.
He may backspace when there is no characters left, and nothing will happen.
1 <= n <= 4
The total length of n words <= 100000
The length of the operation sequence <= 100000
The words and the sequence only contains lower case letter.
输出描述:
You should output L +1 integers, where L is the length of the operation sequence.
The i-th(index from 0) is the minimum characters to achieve the goal, after the first i operations.
不难发现,每个位置的答案就是当前查询串的后缀与所有串的前缀去除匹配部分后,剩下长度的min。
前后缀匹配问题,多串,考虑ac自动机。
首先把问题转化到字典树上,就是每个后缀跑到字典树的终止位置,离他最近的叶子节点深度-当前节点深度。
再放到ac自动机上,就不需要去跑每一个后缀了,显然,答案是从当前节点往上一条fail链上的上述答案取min。
因此,首先对字典树部分从下往上更新,做一个简单dp,求出离他最近的叶子节点深度-当前节点深度,当前正确答案 = min(当前记录的答案,子节点记录的答案+1)。
之后再更新fail树,这次从上往下更新,当前真·正确答案 = min(当前记录答案,父节点传递下来的答案)。
由于trie图已经天然解决了失配问题,直接把查询串放到ac自动机上跑,直接输出当前节点的ans即可。
题目中有一个删除字符的操作,并没有想到什么好的解决方法,因此用了一个栈来记录操作,遇到删除时手动取栈顶回退。
板子太挫了,我敲模板的时候根本不会这些操作…要修改一下模板。
走在去地铁的路上感觉这题跟wf那题有点像,应该也能用广义sam来做。
然后就发现输入的查询串其实是一个倒序字典树(这就跟wf题一模一样了),在出现“-”符号时分叉。
感觉对查询串建字典树,之后bfs字典树建广义sam,再把其他的串丢上广义sam去,倒着跑(这样就解决了倒序字典树的问题)。
sam也要更新出一个答案,至于怎么更新,感觉好像很简单,之后补了算了。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n, minn = maxn;
string s, p;
struct AC_Automaton {
int next[maxn][26];
int pre[maxn];
int ans[maxn];
int fail[maxn];
int sz, root;
int newNode() {
for (int i = 0; i < 26; i++) {
next[sz][i] = -1;
}
fail[sz] = -1;
return sz++;
}
void init() {
sz = 0;
root = newNode();
memset(ans, 0x3f, sizeof(ans));
}
void add() {
int p = root, c;
for (int i = 0; i < s.length(); i++) {
c = s[i] - 'a';
if (next[p][c] == -1) {
next[p][c] = newNode();
}
pre[next[p][c]] = p;
p = next[p][c];
}
ans[p] = 0;
}
void getFail() {
queue<int> q;
for (int i = 0; i < 26; i++) {
if (~next[root][i]) {
fail[next[root][i]] = root;
q.push(next[root][i]);
} else {
next[root][i] = root;
}
}
while (!q.empty()) {
int p = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (~next[p][i]) {
fail[next[p][i]] = next[fail[p]][i];
q.push(next[p][i]);
} else {
next[p][i] = next[fail[p]][i];
}
}
}
}
void solve() {
for (int i = sz; i > root; --i) {
int e = pre[i];
ans[e] = min(ans[e], ans[i] + 1);
}
for (int i = root + 1; i < sz; ++i) {
int e = fail[i];
ans[i] = min(ans[i], ans[e]);
}
int now = root;
stack<int> back;
cout << ans[root] << '\n';
for (int i = 0; i < p.length(); ++i) {
if (p[i] == '-') {
if(!back.empty()){
now = back.top();
back.pop();
}
cout << ans[now] << '\n';
continue;
}
back.push(now);
now = next[now][p[i] - 'a'];
cout << ans[now] << '\n';
}
}
} ac;
int main() {
ios::sync_with_stdio(0);
cin >> n;
ac.init();
while (n--) {
cin >> s;
ac.add();
}
ac.getFail();
cin >> p;
ac.solve();
return 0;
}