后缀自动机我已经给zxa和hzt都讲了一遍了, 就不在这里再重复了。
后缀自动机做的事情就是用 O(n) 的时间建出来一棵后缀树, 然后就可以用后缀树做很多可爱的事情啦 ~\(≧▽≦)/~
后缀自动机的空间占用是 lenth * 2 * (字符种类), 相比于 ac自动机的 lenth(子串) * (字符种类)* (字符种类)也不算差, 但是它的功能要强大得多。
后缀自动机的一个特点就是, 对于任意一个子串, 都可以从根向下把它走出来, 所以可以再处理一个串所有子串的种种性质的时候非常方便。
spoj 1811 LCS
求两个串的 最长子串。
先建出来一个串的后缀自动机, 然后让另一个串在上面跑一个类似于后缀自动机的东西。
考虑把第二个串上的点一个一个地加进去, 加到第i 个字符为c , 这时肯定要像建后缀自动机的时候一样, 找到当前节点 深度最大的一个祖先使得 trans[p][c] 不为0, 然后跳到 trans[p][c] 那里。 但是如果祖先已经找到根了但是trans[p][c] 还是0 , 就必须要从头再开始找一遍了。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#define MAXN 500005
using namespace std;
char s1[MAXN], s2[MAXN];
int cnt, last, len[MAXN], trans[MAXN][26], fat[MAXN];
void add(int x){
int c = s1[x] - 'a', np = ++ cnt, p = last;
last = np; len[np] = x;
for(; !trans[p][c]; p = fat[p])trans[p][c] = np;
if(!p)fat[np] = 1;
else{
int q = trans[p][c];
if(len[q] == len[p] + 1) fat[np] = q;
else{
int nq = ++ cnt; len[nq] = len[p] + 1;
memcpy(trans[nq], trans[q], sizeof(trans[q]));
fat[nq] = fat[q];
fat[q] = fat[np] = nq;
for(;trans[p][c] == q; p = fat[p])trans[p][c] = nq;
}
}
}
int main(){
scanf("%s%s", s1 + 1, s2 + 1);
last = ++ cnt;
int n = strlen(s1 + 1), n2 = strlen(s2 + 1);
for(int i = 1; i <= n; i ++)add(i);
int p = 1, nlen = 0, ans = 0;
fat[1] = -1;
for(int i = 1; i <= n2; i ++){
int c = s2[i] - 'a';
if(trans[p][c]){p = trans[p][c]; nlen ++;} // 可以向前走
else{
while(p != -1 && !trans[p][c])p = fat[p]; // 找到第一个trans[p][c]不为0的祖先
if(p == -1)p = 1, nlen = 0; // 从新开始找
else{
nlen = len[p] + 1;
p = trans[p][c];
}
}ans = max(ans, nlen);
}cout<<ans<<endl;
return 0;
}
spoj 8222 NSUBSTR
求每个指定长度的串最多出现多少次, 也是后缀自动机的经典模板了。
这道题有告诉我们了一个后缀自动机的好性质:一个点的 子节点中接受态节点的个数就是这个点 在这个串中出现的次数。 这个还是挺显然的吧。
那么我们只要找到 所有入度为0 的点(即 没有 点的fat 是它的点), 然后拓扑上去就可以简单地转移出 每个串出现了多少次了。
当然, 也可以把每个串 的lenth 排序然后dp, 但是这道题基数排序好像要比拓扑慢一点不知道为什么。
在zyb的帮助下知道 当输出的数量很多的时候进行输出优化(存在一个string里然后puts())可以快很多呢
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#define MAXN 510005
using namespace std;
char s[MAXN], ans[MAXN * 20];
int n, cnt, q[MAXN], last, head = 1, tail, len[MAXN], fat[MAXN], zi[MAXN], ind[MAXN], f[MAXN], trans[MAXN][27];
void add(int x){
int c = s[x] - 'a', p = last, np = ++ cnt;
last = np;len[np] = x; zi[np] = 1;
for(; p && !trans[p][c]; p = fat[p])trans[p][c] = np;
if(!p)fat[np] = 1;
else{
int q = trans[p][c];
if(len[p] + 1 == len[q])fat[np] = q;
else{
int nq = ++ cnt; len[nq] = len[p] + 1;
memcpy(trans[nq], trans[q], sizeof(trans[q]));
fat[nq] = fat[q];
fat[np] = fat[q] = nq;
for(; trans[p][c] == q; p = fat[p])trans[p][c] = nq;
}
}
}
int main(){
scanf("%s", s + 1);
n = strlen(s + 1);
last = ++ cnt;
for(int i = 1; i <= n; i ++)add(i);
for(int i = 1; i <= cnt; i ++)ind[fat[i]] ++;
for(int i = 1; i <= cnt; i ++)if(!ind[i])q[++ tail] = i;
while(head <= tail){
int u = q[head]; head ++;
if(!u)break;
int v = fat[u]; ind[v] --;
if(!ind[v])q[++ tail] = v;
zi[v] += zi[u];
}
for(int i = 1; i <= cnt; i ++)f[len[i]] = max(f[len[i]], zi[i]);
for(int i = n; i >= 1; i --)f[i] = max(f[i], f[i + 1]);
/* for(int i = 1; i <= n; i ++)printf("%d\n", f[i]);*/
cnt = -1;
int a[10];
for(int i = 1; i <= n; i ++){
int aa = 0; while(f[i])a[++ aa] = f[i] % 10, f[i] /= 10;
for(int j = aa; j >= 1; j --)ans[++ cnt] = a[j] + '0';
ans[++ cnt] = '\n';
}
puts(ans);
return 0;
}
spoj 1812 LCS2
基本和LCS那题一样, 但是要注意的一点就是你每访问到一个点, 它的祖先就都可以被访问而我们在计算时是只考虑到了最深的那个, 所以扫完了每个串还要再次更新一下, 而更新它的祖先就又需要对这棵后缀树拓扑排一遍, 写起来感觉像是前两题的结合。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 200005
using namespace std;
int cnt, last, fat[MAXN], trans[MAXN][26], l[MAXN], f[MAXN], minn[MAXN], q[MAXN], head = 1, tail, ind[MAXN];
char s[MAXN];
inline void insert(int x){
int c = s[x] - 'a';
int p = last, np = ++ cnt; last = np;
l[np] = x;
while(!trans[p][c])trans[p][c] = np,p = fat[p];
if(!p)fat[np] = 1;
else{
int q = trans[p][c];
if(l[q] == l[p] + 1)fat[np] =q;
else{
int nq = ++ cnt; l[nq] = l[p] + 1;
memcpy(trans[nq], trans[q], sizeof(trans[q]));
fat[nq] = fat[q];
fat[q] = fat[np] = nq;
while(trans[p][c] == q)trans[p][c] = nq, p = fat[p];
}
}
}
int main(){
gets(s + 1);
int len = strlen(s + 1);
last = ++ cnt;
for(int i = 1; i <= len; i ++)insert(i);
for(int i = 1; i <= cnt; i ++)ind[fat[i]] ++;
for(int i = 1; i <= cnt; i ++)if(!ind[i])q[++ tail] = i;
while(head <= tail){
int u = q[head]; head ++; if(!u)break;
ind[fat[u]] --;
if(!ind[fat[u]])q[++ tail] = fat[u];
}
for(int i = 1; i <= cnt; i ++)minn[i] = l[i];
fat[1] = -1;
while(gets(s + 1) != NULL){
memset(f, 0, sizeof(f));
len = strlen(s + 1);
int p = 1, nl = 0;
for(int x = 1; x <= len; x ++){
int c = s[x] - 'a';
if(trans[p][c])p = trans[p][c], nl ++, f[p] = max(f[p], nl);
else{
while(p != -1 && !trans[p][c])p = fat[p];
if(p == -1)p = 1, nl = 0;
else{
nl = l[p] + 1, p = trans[p][c], f[p] = max(f[p], nl);
}
}
}
for(int i = 1; i <= cnt; i ++){
int x = q[i];
if(f[x])f[fat[x]] = l[fat[x]];
}
for(int i = 1; i <= cnt; i ++)minn[i] = min(minn[i], f[i]);
}
int ans = 0;
for(int i = 1; i <= cnt; i ++)ans = max(ans, minn[i]);
cout<<ans<<endl;
return 0;
}