后缀自动机

后缀自动机我已经给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;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值