1 条题解
-
0
自动搬运
来自洛谷,原作者为

CYJian
今日はこっちの地方はどしゃぶりの晴天でした,昨日もずっと暇で一日満喫してました搬运于
2025-08-24 22:19:04,当前版本为作者最后更新于2020-10-19 10:43:31,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题目大意
给定一棵 个节点的
Trie树,进行 次询问,每次询问一个串 与Trie树上从任意一个节点开始走到根路径上形成的串中,有多少个串满足 是其前缀。题目解法
考虑
Trie树上每一个串对所有询问串的贡献。不难发现,如果我们对询问串的反串建AC自动机(此时要求的条件是询问串是Trie树上的串的后缀),那么对于一个串 ,它在AC自动机中出现过的所有后缀,一定是fail树上的一条到根节点的链。我们只需要用 在AC自动机上暴力匹配,就能找到最长的那一个后缀。打一个前缀和标记最后进行一遍DFS即可。现在我们需要对
Trie树上所有点代表的字符串跑一遍匹配。由于Trie树上的子节点所代表的串在AC自动机上跑到的匹配是可以直接用其父节点匹配的位置跳一步转移就可以到达的。利用Trie图的小trick就能做到 。Code:const int MAXN = 1000010; int tr[MAXN][26]; int res[MAXN]; int pos[MAXN]; char str[MAXN]; vector<int>to[MAXN]; struct ACAM { int tr[MAXN][26]; int fail[MAXN]; int cnt[MAXN]; int ct; inline int Insert(int n) { int x = 0; for(int i = n; i >= 1; i--) { int c = str[i] - 'A'; if(!tr[x][c]) tr[x][c] = ++ct; x = tr[x][c]; } return x; } inline void build() { queue<int>q; for(int i = 0; i < 26; i++) if(tr[0][i]) q.push(tr[0][i]); while(!q.empty()) { int x = q.front(); q.pop(); for(int c = 0; c < 26; c++) if(tr[x][c]) fail[tr[x][c]] = tr[fail[x]][c], q.push(tr[x][c]); else tr[x][c] = tr[fail[x]][c]; } for(int i = 1; i <= ct; i++) to[fail[i]].push_back(i); } inline void dfs(int x) { int sz = to[x].size(); for(int i = 0; i < sz; i++) dfs(to[x][i]), cnt[x] += cnt[to[x][i]]; } }ac; inline void dfs(int x, int y) { ac.cnt[y]++; for(int c = 0; c < 26; c++) if(tr[x][c]) dfs(tr[x][c], ac.tr[y][c]); } int main() { int n = ri, k = ri; for(int i = 1; i <= n; i++) { char ch = ge; while(!isalpha(ch)) ch = ge; tr[ri][ch - 'A'] = i; } for(int i = 1; i <= k; i++) { char ch = ge; int len = 0; while(!isalpha(ch)) ch = ge; while(isalpha(ch)) str[++len] = ch, ch = ge; pos[i] = ac.Insert(len); } ac.build(), dfs(0, 0), ac.dfs(0); for(int i = 1; i <= k; i++) printf("%d\n", ac.cnt[pos[i]]); return 0; }
- 1
信息
- ID
- 4223
- 时间
- 10000ms
- 内存
- 500MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者