后缀自动机学习
后缀自动机就是能识别一个串的所有后缀,附加功能是可以识别所有字串,还能统计字串个数。
后缀自动机的parent是指该节点的父亲,len表示这个节点能表示的最长字串的长度
child[][]表示该节点添加一个字符能到达的状态。。
spoj1811 求两个串的最长公共字串
解法:用a串建立后缀自动机,然后用b串在自动机上跑,其中parent数组记录节点的父亲,len记录该节点能表示的最大字串长度
定义长度为num,如果遇到字符x存在孩子child[u]那么num++,否则找父亲节点,直到找到有child[u]的节点
同时令num=len[u]+1(父亲节点的len值一定是跟从根到该节点长度一致的的)
如果失配了,也就是访问到根,那么num=0,不断记录num的最大值即可
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 510000
struct Auto_Sufix{
int parent[maxn];
int child[maxn][26];
int len[maxn];
int last;
int cnt;
int newNode(){
memset(child[cnt],0,sizeof(child[cnt]));
len[cnt] = parent[cnt] = 0;
return cnt++;
}
void init(){
last = cnt = 1;
newNode();
}
void add(int x){
int np = newNode(),pa = last;
len[np] = len[last]+1;
while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa];
if(pa == 0) parent[np] = 1;
else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x];
else {
int nq = newNode(),p=child[pa][x];
memcpy(child[nq],child[p],sizeof(child[p]));
len [nq] = len[pa]+1;
parent[nq] = parent[p];
parent[np] = parent[p] = nq;
for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]);
}
last = np;
}
int getmore(){
int pa = parent[last];
return len[last]-len[pa];
}
int getMaxCommon(char*word){
int len1 = strlen(word);
int ans = 0,num=0,u=1,x;
for(int i = 0;i < len1; i++){
x = word[i]-'a';
if(child[u][x] != 0 ){
num++,u=child[u][x];
}
else {
while(u&&!child[u][x]) u=parent[u];
if(u==0)u=1,num=0;
else num=len[u]+1,u=child[u][x];
}
ans=max(ans,num);
}
return ans;
}
};
Auto_Sufix sufix;
char word[maxn];
int main(){
while(scanf("%s",word)!=EOF){
sufix.init();
int len = strlen(word);
for(int i = 0;i < len ;i++)
sufix.add(word[i]-'a');
scanf("%s",word);
int ans= sufix.getMaxCommon(word);
printf("%d\n",ans);
}
return 0;
}
spoj1812 求多个串的最长公共字串
跟上一题类似,先建立一个自动机,然后用其他串在上面跑
用ans数组记录当前串到每个状态得到的最大长度,用原来的自动机的len记录所有串到达这些状态的公共长度的最大值
但是在跟新len值的时候需要注意:因为一个状态能表示的长度大于其父亲状态的长度,如果到达这个状态了,说明其
父亲状态也是可达,同时父亲的父亲。。。也是可达的。所以还需要跟新父亲状态的ans值
因为后缀自动机的节点是一个拓扑图,没有环路的有向图,根据定义len越大的状态能表示的字串越多,其父亲的状态的len
必然小于他的len值,对这些状态的len值拍个序,就能实现逆拓扑序了,采用基数排序,可以做到o(n)的排序
然后用len值最大的节点跟新len,在用这个节点跟新该父亲的ans即可
top[i]记录的就是len值排在i位的节点的下标
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 210000
struct Auto_Sufix{
int parent[maxn];
int child[maxn][26];
int len[maxn];
int last;
int cnt;
int newNode(){
memset(child[cnt],0,sizeof(child[cnt]));
len[cnt] = parent[cnt] = 0;
return cnt++;
}
void init(){
last = cnt = 1;
newNode();
}
void add(int x){
int np = newNode(),pa = last;
len[np] = len[last]+1;
while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa];
if(pa == 0) parent[np] = 1;
else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x];
else {
int nq = newNode(),p=child[pa][x];
memcpy(child[nq],child[p],sizeof(child[p]));
len [nq] = len[pa]+1;
parent[nq] = parent[p];
parent[np] = parent[p] = nq;
for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]);
}
last = np;
}
int getmore(){
int pa = parent[last];
return len[last]-len[pa];
}
};
Auto_Sufix sufix;
char word[maxn];
int ans[maxn];
int top[maxn];
int main(){
gets(word);
sufix.init();
int len = strlen(word);
for(int i = 0;i < len ;i++)
sufix.add(word[i]-'a');
memset(ans,0,sizeof(ans));
int t = 0;
for(int i = 0;i < sufix.cnt;i++) ans[i]=0;
for(int i = 0;i < sufix.cnt;i++) ans[sufix.len[i]]++;
for(int i = 1;i < sufix.cnt;i++) ans[i] += ans[i-1];
for(int i = sufix.cnt-1;i>0;i--) top[--ans[sufix.len[i]]]=i;
memset(ans,0,sizeof(ans));
while(gets(word)){
int len1 = strlen(word);
int num=0,u=1,x;
for(int i = 0;i < len1; i++){
x = word[i]-'a';
if(sufix.child[u][x] != 0 ){
num++,u=sufix.child[u][x];
}
else {
while(u&&!sufix.child[u][x]) u=sufix.parent[u];
if(u==0)u=1,num=0;
else num=sufix.len[u]+1,u=sufix.child[u][x];
}
ans[u] = max(ans[u],num);
}
for(int i = sufix.cnt-1;i>1;i--){
u = top[i];
sufix.len[u] = min(sufix.len[u],ans[u]);
ans[sufix.parent[u]] = max(ans[sufix.parent[u]],ans[u]);
ans[u] = 0;
}
}
int res = 0 ;
for(int i = 2;i < sufix.cnt;i++)
res = max(res,sufix.len[i]);
printf("%d\n",res);
return 0;
}
hdu 4416Good Article Good sentence
求一个字符串A 的所有不重复字串的个数,同时这些子串不出现在s1,s2,.....sn中
同样用A建立一个自动机,然后开一个len1数组,记录的是s1到sn在这个自动机上跑能得到字串长度的最大值
同样用逆拓扑序遍历最后生成的len1数组,且需要跟新每个节点的父亲节点。记录len1[u]-len[parenrt[u]]]表示到达的这个状态最多可以表示
几个字串,可能为负数,因为不可达,那么取0和该值得最大值。
父亲节点的值为len1[u]和len[paren[u]]的最小值,防止重复计算
然后用a串的字串数-重复的字串数就是答案,可能超int
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200001
int parent[maxn],child[maxn][26],len[maxn],len1[maxn];
int cnt,last;
int newNode(){
memset(child[cnt],0,sizeof(child[cnt]));
len[cnt] = parent[cnt] = 0;
return cnt++;
}
void init(){
cnt = last = 1;
newNode();
}
void add(int x){
int pa = last, np = newNode();
len[np] = len[pa]+1;
last = np;
while(pa && !child[pa][x]) child[pa][x] = np,pa=parent[pa];
if(pa == 0) parent[np] = 1;
else if(len[child[pa][x]] == len[pa]+1) parent[np] = child[pa][x];
else {
int nq = newNode(), p = child[pa][x];
memcpy(child[nq],child[p],sizeof(child[nq]));
parent[nq] = parent[p];
len[nq] = len[pa]+1;
parent[p] = parent[np] = nq;
while(pa && child[pa][x] == p) child[pa][x]=nq,pa=parent[pa];
}
}
#define ll long long
ll getmore(){
ll ans = 0;
for(int i = 2;i < cnt;i++)
ans += len[i]-len[parent[i]];
return ans;
}
char word[maxn];
int bucket[maxn],top[maxn];
int main(){
int t,n,lenth;
scanf("%d",&t);
for(int tt = 1; tt <= t; tt++){
scanf("%d\n",&n);
gets(word);
lenth = strlen(word);
init();
for(int i = 0;i < lenth; i++)
add(word[i]-'a');
ll ans = getmore();
for(int i = 0;i < cnt; i++)len1[i] = 0;
while(n--){
gets(word);
lenth = strlen(word);
int u=1,x,sum=0;
for(int i = 0;i < lenth; i++){
x = word[i]-'a';
if(child[u][x] != 0) u = child[u][x],sum++;
else {
while(u&&child[u][x]==0)u=parent[u];
if(u == 0) u=1,sum=0;
else sum=len[u]+1,u=child[u][x];
}
len1[u] = max(sum,len1[u]);
}
}
for(int i = 0;i < cnt;i++)bucket[i] = 0;
for(int i = 0;i < cnt;i++)bucket[len[i]]++;
for(int i = 1;i < cnt;i++)bucket[i]+=bucket[i-1];
for(int i = cnt-1;i >=0;i--)top[--bucket[len[i]]]=i;
long long res = 0;
int u,n;
for(int i = cnt-1;i > 1;i--){
u = top[i];
// if(len1[u] == 0) res+=len[u]-len[parent[u]];
// else if(len1[u]<len[u])res+=len[u]-len1[u];
// len1[parent[u]] = max(len1[parent[u]],len1[u]);
n = max(0,len1[u]-len[parent[u]]);
res+=n;
if(parent[u])len1[parent[u]] = min(len[parent[u]],max(len1[u],len1[parent[u]]));
}
printf("Case %d: %I64d\n",tt,ans-res);
}
return 0;
}
hdu 4622Reincarnation多校题
给一个字符串,给一些询问求在某一个区间字串个数
后缀自动机枚举不同起点,ans[i][j]表示从i到j的字串个数
然后在线回答即可。离线回答可能快一些,可以减去一些不必要的起点
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 4007
struct Auto_Sufix{
int parent[maxn];
int child[maxn][26];
int len[maxn];
int last;
int cnt;
int newNode(){
memset(child[cnt],0,sizeof(child[cnt]));
len[cnt] = parent[cnt] = 0;
return cnt++;
}
void init(){
last = cnt = 1;
newNode();
}
void add(int x){
int np = newNode(),pa = last;
len[np] = len[last]+1;
last = np;
while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa];
if(pa == 0) parent[np] = 1;
else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x];
else {
int nq = newNode(),p=child[pa][x];
memcpy(child[nq],child[p],sizeof(child[p]));
len [nq] = len[pa]+1;
parent[nq] = parent[p];
parent[np] = parent[p] = nq;
for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]);
}
}
int getmore(){
int pa = parent[last];
return len[last]-len[pa];
}
};
Auto_Sufix sufix;
char word[maxn];
int ans[maxn][maxn];
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%s",word);
memset(ans,0,sizeof(ans));
int n = strlen(word);
for(int i = 0;i < n; i++){
sufix.init();
for(int j = i;j < n; j++){
sufix.add(word[j]-'a');
ans[i][j] = ans[i][j-1]+sufix.getmore();
}
}
int q,b,t;
scanf("%d",&q);
while(q--){
scanf("%d%d",&b,&t);
printf("%d\n",ans[b-1][t-1]);
}
}
return 0;
}
hdu 4436 str2int
给一些数字字符串,求这些字符串的字串(不重复的,没有前缀0)的和。
将这些字符串用一个连接符连接起来,然后建立后缀自动机
然后按照len值的大小排序
因为不能有前缀0,所以根节点不能访问0的孩子节点
用sum[u]标记u这个所有能到这个点的数值之和,num[u]表示有多少个路径到这里
那么sum[u]就是以u结束的数的和然后转移的时候,sum[u]*10+num[u]*j j表示后面接的数字
所有合法的sum[u]之和就是答案
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200007
#define mod 2012
int parent[maxn],child[maxn][11],len[maxn];
int cnt,last;
int newNode(){
memset(child[cnt],0,sizeof(child[cnt]));
len[cnt] = parent[cnt] = 0;
return cnt++;
}
void init(){
cnt = last = 1;
newNode();
}
void add(int x){
int pa = last, np = newNode();
len[np] = len[pa]+1;
last = np;
while(pa && !child[pa][x]) child[pa][x] = np,pa=parent[pa];
if(pa == 0) parent[np] = 1;
else if(len[child[pa][x]] == len[pa]+1) parent[np] = child[pa][x];
else {
int nq = newNode(), p = child[pa][x];
memcpy(child[nq],child[p],sizeof(child[nq]));
parent[nq] = parent[p];
len[nq] = len[pa]+1;
parent[p] = parent[np] = nq;
while(pa && child[pa][x] == p) child[pa][x]=nq,pa=parent[pa];
}
}
char word[maxn];
int bucket[maxn],top[maxn];
int num[maxn],sum[maxn];
int main(){
int t;
while(scanf("%d",&t)!=EOF){
int len1 = 0;
for(int i = 0;i < t;i++){
scanf("%s",&word[len1]);
len1 += strlen(&word[len1]);
word[len1++]='0'+10;
}
init();
for(int i = 0;i < len1;i++)
add(word[i]-'0');
memset(bucket,0,sizeof(bucket));
for(int i = 1;i < cnt; i++)bucket[len[i]]++;
for(int i = 1;i < cnt; i++)bucket[i]+=bucket[i-1];
for(int i = cnt-1;i > 0;i--)top[--bucket[len[i]]]=i;
memset(num,0,sizeof(num));
memset(sum,0,sizeof(sum));
num[1]=1;
int ans = 0,v,res,u,j;
for(int i = 0;i<cnt-1;i++){
u = top[i];
for(j = 0;j < 10;j++){
if(j == 0 && u == 1) continue;
if(child[u][j]==0)continue;
v = child[u][j];
num[v]+=num[u];
if(num[v] >= mod)num[v]-=mod;
res = sum[u]*10 + num[u]*j;
sum[v]+=res;
sum[v]%=mod;
}
ans+=sum[u];
if(ans >= mod)ans-=mod;
}
ans%=mod;
printf("%d\n",ans);
}
return 0;
}