hihocoder后缀自动机2~6的解析(后缀自动机SAM入门)

后缀自动机2:hihocoder1445.
题目大意:求串 S S S本质不同的子串个数.
1 ≤ ∣ S ∣ ≤ 1 0 6 1\leq |S|\leq 10^6 1S106.

考虑后缀自动机的原理,确定一个串的方式为确定该串在原串中的右端点和该串的长度,所以只有当两个子串在同一个Right集合中且它们的长度相同才会相同.那么直接构造SAM得到每个节点 i i i表示的长度区间 [ l e n p a r i , l e n i ] [len_{par_i},len_i] [lenpari,leni],点 i i i的贡献即为 l e n i − l e n p a r i len_i-len_{par_i} lenilenpari.

时间复杂度 O ( ∣ S ∣ Σ ) O(|S|\Sigma) O(SΣ).

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=1000000,C=26;

int n;
char s[N+9];
struct automaton{
  int s[C],par,len;
}tr[N*2+9];
int cn,last;
LL ans;

void Build(){cn=last=1;}

void Extend(int x){
  int p=last,np=++cn;
  tr[np].len=tr[p].len+1;
  for (;p&&!tr[p].s[x];p=tr[p].par) tr[p].s[x]=np;
  if (!p) tr[np].par=1;
  else{
  	int q=tr[p].s[x];
  	if (tr[p].len+1==tr[q].len) tr[np].par=q;
  	else{
  	  tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
  	  tr[np].par=tr[q].par=cn;
  	  for (;p&&tr[p].s[x]==q;p=tr[p].par) tr[p].s[x]=cn;
  	}
  }
  last=np;
}

Abigail into(){
  scanf("%s",s+1);
  n=strlen(s+1);
}

Abigail work(){
  Build();
  for (int i=1;i<=n;++i) Extend(s[i]-'a');
  for (int i=1;i<=cn;++i) ans+=tr[i].len-tr[tr[i].par].len;
}

Abigail outo(){
  printf("%lld\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

后缀自动机3:hihocoder1449.
题目大意:求串 S S S长度为 k k k的所有子串中出现次数最多的串的出现次数,并求出对于所有 1 1 1 n n n的答案.
1 ≤ ∣ S ∣ ≤ 1 0 6 1\leq |S|\leq 10^6 1S106.

很容易想到对于一个节点 i i i,设它代表的长度区间为 [ m n i , m x i ] [mn_i,mx_i] [mni,mxi],Right集合大小为 R i R_i Ri,并且设答案序列为 a n s [ k ] ans[k] ans[k].那么这个节点相当于是对 [ m n i , m x i ] [mn_i,mx_i] [mni,mxi]这个区间的所有 a n s ans ans值与 R i R_i Ri取了一个最大值.

发现这是个很经典的问题,可以形式化地写成在一个序列上支持两个操作:
1.求 a n s [ i ] ans[i] ans[i]的值.
2.对于 ∀ j ∈ [ m n i , m x i ] \forall j\in[mn_i,mx_i] j[mni,mxi],让 a n s [ j ] = max ⁡ ( a n s [ j ] , R i ) ans[j]=\max(ans[j],R_i) ans[j]=max(ans[j],Ri).

发现这个东西可以用线段树直接解决,但是线段树的大常数在这种情况下可能会TLE,而且这个线段树要用永久化标记才能搞.

所以考虑这个问题的性质,注意到对于一个串 S S S S S S的任意一个子串的出现次数必定不小于 S S S的出现次数,也就是说 a n s [ i ] ans[i] ans[i]是非严格递减的.那么我们考虑对于一个区间修改,可以直接转化为一个前缀的修改,这个时候直接让 a n s [ m x i ] = max ⁡ ( a n s [ m x i ] , R i ) ans[mx_i]=\max(ans[mx_i],R_i) ans[mxi]=max(ans[mxi],Ri),最后从后往前枚举让 a n s [ i ] = max ⁡ j = i n a n s [ j ] ans[i]=\max_{j=i}^{n}ans[j] ans[i]=maxj=inans[j],就可以 O ( ∣ S ∣ Σ ) O(|S|\Sigma) O(SΣ)解决这个问题了.

代码如下:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N=1000000,C=26;

char s[N+9];
int n;

void into(){
  scanf("%s",s+1);
  n=strlen(s+1);
}

struct automaton{
  int s[C],par,len;
}tr[N*2+9];
int cn,last,rgh[N*2+9];

void Build(){cn=last=1;}

void Extend(int c){
  int p=last,np=++cn;
  tr[last=np].len=tr[p].len+1;rgh[np]=1;
  for (;p&&!tr[p].s[c];p=tr[p].par) tr[p].s[c]=np;
  if (!p) tr[np].par=1;
  else{
    int q=tr[p].s[c];
    if (tr[p].len+1==tr[q].len) tr[np].par=q;
    else{
      tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
      tr[q].par=tr[np].par=cn;
      for (;p&&tr[p].s[c]==q;p=tr[p].par) tr[p].s[c]=cn;
    }
  }
}

void Get_tr(){
  Build();
  for (int i=1;i<=n;++i) Extend(s[i]-'a');
}

int cnt[N+9],ord[N*2+9];

void Get_rgh(){
  for (int i=1;i<=cn;++i) ++cnt[tr[i].len];
  for (int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
  for (int i=1;i<=cn;++i) ord[cnt[tr[i].len]--]=i;
  for (int i=cn;i>=1;--i){
    int t=ord[i];
    rgh[tr[t].par]+=rgh[t];
  }
}

int ans[N+9];

void Get_ans(){
  for (int i=2;i<=cn;++i){
    int t=tr[i].len;
    ans[t]=max(ans[t],rgh[i]);
  }
  for (int i=n;i>=1;--i) ans[i]=max(ans[i],ans[i+1]);
}

void work(){
  Get_tr();
  Get_rgh();
  Get_ans();
}

void outo(){
  for (int i=1;i<=n;++i)
    printf("%d\n",ans[i]);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

后缀自动机4:hihocoder1457.
题目大意:给定一些数字串 S i S_i Si,求 S i S_i Si中所有本质不同的子串(在 S i , S j ( i = ̸ j ) S_i,S_j(i=\not{}j) Si,Sj(i=j)中都出现过算作一次)的和,对 1 0 9 + 7 10^9+7 109+7取模.
∑ S i ≤ 1 0 6 \sum S_i\leq 10^6 Si106

先考虑单个串的情况.看到本质不同的子串肯定SAM,然后考虑建出来SAM的性质.

首先考虑SAM的性质,发现SAM必然是一个跑不出重复子串的自动机,那么考虑直接把后缀自动机的转移图建出来,在这上面跑DP,设 f [ i ] f[i] f[i]表示转移到状态 i i i得到本质不同的所有子串的和, c n t [ i ] cnt[i] cnt[i]表示转移到状态 i i i得到本质不同的所有子串的数量一条边 ( y , x , v ) (y,x,v) (y,x,v)表示从点 y y y到点 x x x,边权为 v v v.那么就可以这么转移:
c n t [ x ] = ∑ ( y , x , v ) ∈ E c n t [ y ] f [ x ] = ∑ ( y , x , v ) ∈ E f [ y ] ∗ 10 + c n t [ y ] ∗ v cnt[x]=\sum_{(y,x,v)\in E}cnt[y]\\ f[x]=\sum_{(y,x,v)\in E}f[y]*10+cnt[y]*v cnt[x]=(y,x,v)Ecnt[y]f[x]=(y,x,v)Ef[y]10+cnt[y]v

有了这个转移后,我们只需要构造出SAM即可.

多个字符串的话直接上广义SAM即可.

时间复杂度均为 O ( ∑ ∣ S i ∣ Σ ) O(\sum |S_i|\Sigma) O(SiΣ).

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=1000000,C=10,mod=1000000007; 

int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
int mul(int a,int b){return (LL)a*b%mod;}
void sadd(int &a,int b){a=add(a,b);}
void ssub(int &a,int b){a=sub(a,b);}
void smul(int &a,int b){a=mul(a,b);}

int n,mx;
char s[N+9];
struct automaton{ 
  int s[C],par,len;
}tr[N*2+9];
int cn,last;

void Build(){cn=last=1;}
void Rebuild(){last=1;}

void Extend(int x){
  int p=last;
  if (tr[p].s[x]){
    int q=tr[p].s[x];
    if (tr[p].len+1==tr[q].len) last=q;
    else{
      tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
      tr[q].par=cn;
      for (;p&&tr[p].s[x]==q;p=tr[p].par) tr[p].s[x]=cn;
      last=cn;
    }
  }else{
    int np=++cn;
    tr[np].len=tr[p].len+1;
    for (;p&&!tr[p].s[x];p=tr[p].par) tr[p].s[x]=np;
    if (!p) tr[np].par=1;
    else{
      int q=tr[p].s[x];
      if (tr[p].len+1==tr[q].len) tr[np].par=q;
      else{
        tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
        tr[np].par=tr[q].par=cn;
        for (;p&&tr[p].s[x]==q;p=tr[p].par) tr[p].s[x]=cn;
	  }
    }
    last=np;
  }
}

int cnt[N*2+9],ord[N*2+9],dp[N*2+9];

void Topsort(){
  for (int i=1;i<=cn;++i) ++cnt[tr[i].len];
  for (int i=1;i<=mx;++i) cnt[i]+=cnt[i-1];
  for (int i=1;i<=cn;++i) ord[cnt[tr[i].len]--]=i;
  for (int i=1;i<=mx;++i) cnt[i]=0;
  cnt[1]=1;
  for (int i=1;i<=cn;++i){
    int t=ord[i];
    for (int j=0;j<C;++j)
      if (tr[t].s[j]){
        sadd(cnt[tr[t].s[j]],cnt[t]);
        sadd(dp[tr[t].s[j]],add(mul(dp[t],10),mul(j,cnt[t])));
      }
  }
}

int ans;

Abigail into(){
  scanf("%d",&n);
  Build();
  for (int i=1;i<=n;++i){
    scanf("%s",s+1);
    int m=strlen(s+1);
    mx=max(mx,m);
    Rebuild();
    for (int j=1;j<=m;++j) Extend(s[j]-'0');
  }
}

Abigail work(){
  Topsort();
  for (int i=1;i<=cn;++i) sadd(ans,dp[i]);
}

Abigail outo(){
  printf("%d\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

后缀自动机5:hihocoder1465.
题目大意:给定一个串 S S S,和多个串 T i T_i Ti,求对于每个 T i T_i Ti,在 S S S中有多少个子串与 T i T_i Ti是循环同构串.
1 ≤ ∣ S ∣ , ∑ T i ≤ 1 0 5 1\leq|S|,\sum T_i\leq 10^5 1S,Ti105.

首先若只要求有多少个子串与 T i T_i Ti相同,AC自动机即可,但是这里是要求循环同构串怎么办?

考虑把每一个 T i T_i Ti倍长,那么任意一个下标开始 n n n个字符与 S S S的一个子串相等,这个子串就有贡献.

考虑对 S S S建立SAM,然后直接把每个倍长后的 T i T_i Ti放到自动机里跑,若有转移直接转移,没有就跳parent指针,所有经过的状态都打上标记,最后所有打上标记并且长度区间中包含 n n n的节点的Right集合大小之和即为答案.并且在这个过程中,若到达的某个状态的最短长度大于 n n n了,这个状态也应该跳parent指针.

事实上我们可以把这个过程看作在 T i T_i Ti上设头尾指针,跳转移边相当于尾指针向后,跳parent指针相当于头指针向后,这个过程与在AC自动机上匹配串类似.

再来分析时间复杂度,首先对 S S S建立SAM是 O ( ∣ S ∣ ) O(|S|) O(S)的,对于每个串 T i T_i Ti,每一步至少会使得头指针或尾指针加 1 1 1,而头尾指针最多都到 ∣ T i ∣ |T_i| Ti,也就是说时间复杂度是 O ( ∣ T i ∣ ) O(|T_i|) O(Ti)的,所以总复杂度就是 O ( ∣ S ∣ + ∑ ∣ T i ∣ ) O(|S|+\sum |T_i|) O(S+Ti)的.

值得注意的一点,在转移过程中应该记录当前串的长度,若直接调用SAM上的 l e n len len长度则会WA掉.其实这一点可以通过这个状态可能有更长的串可以表示,但是通过前面那条路径无法转移到这个长度来解释.

时间复杂度 O ( ( ∣ S ∣ + ∑ ∣ T i ∣ ) Σ ) O((|S|+\sum |T_i|)\Sigma) O((S+Ti)Σ).

代码如下:

#include<bits/stdc++.h> 
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=100000,C=26;

struct automaton{
  int s[C],len,par;
}tr[N*2+9];
int cn,last,rght[N*2+9];

void Build_sam(){cn=last=1;}

void extend(int x){
  int p=last,np=++cn;
  tr[np].len=tr[p].len+1;rght[np]=1;
  last=np;
  while (p&&!tr[p].s[x]) tr[p].s[x]=np,p=tr[p].par;
  if (!p) tr[np].par=1;
  else {
  	int q=tr[p].s[x];
  	if (tr[p].len+1==tr[q].len) tr[np].par=q;
  	else {
  	  tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
  	  tr[np].par=tr[q].par=cn;
  	  while (tr[p].s[x]==q&&p) tr[p].s[x]=cn,p=tr[p].par;
  	}
  }
}

struct side{
  int y,next;
}e[N*2+9];
int lin[N*2+9],top;

void ins(int x,int y){
  e[++top].y=y;
  e[top].next=lin[x];
  lin[x]=top;
}

void Build_parent(){
  for (int i=2;i<=cn;++i)
    ins(tr[i].par,i);
}

void Get_rght(int k=1){
  for (int i=lin[k];i;i=e[i].next){
  	Get_rght(e[i].y);
  	rght[k]+=rght[e[i].y];
  }
}

int b[N*2+9],tt[N*2+9],ct;

void add(int x){b[x]=1;tt[++ct]=x;}

int Query(char *c,int len){
  int x=1,ans=0,now=0;
  for (int i=1;i<=len;++i) c[i+len]=c[i];
  for (int i=1;i<=len<<1;++i){
  	while (x&&!tr[x].s[c[i]-'a']) x=tr[x].par,now=tr[x].len;
  	if (!x) x=1,now=0;
  	if (tr[x].s[c[i]-'a']) x=tr[x].s[c[i]-'a'],++now;      //这里不能写成now=tr[x].len,在上面已经解释过原因了
  	if (now>len) while (tr[tr[x].par].len>=len) x=tr[x].par,now=tr[x].len;
  	if (now>=len&&!b[x]) ans+=rght[x],add(x);
  }
  for (;ct;--ct) b[tt[ct]]=0;
  return ans;
}

char s[N*2+9];
int n; 

Abigail into(){
  scanf("%s",s+1);
  n=strlen(s+1);
}

Abigail work(){
  Build_sam();
  for (int i=1;i<=n;++i)
    extend(s[i]-'a');
  Build_parent();
  Get_rght();
}

Abigail getans(){
  int len;
  scanf("%d",&n);
  while (n--){
  	scanf("%s",s+1);
  	len=strlen(s+1);
  	printf("%d\n",Query(s,len));
  }
}

int main(){
  into();
  work();
  getans();
  return 0;
}

后缀自动机6:hihocoder1466.
题目大意:给定两个字符串 A , B A,B A,B,现在要求分别用 A , B A,B A,B的两个子串 X , Y X,Y X,Y进行游戏,问字典序第 K K K小的两个先手必胜的 X , Y X,Y X,Y是哪两个.其中 X , Y X,Y X,Y的字典序比较方式为先比较第一个串的字典序,后比较第二个串的字典序.
游戏规则为,先手开始游戏,两人轮流加字符,先无法操作的人输.其中每一轮操作的规则为,任选 X , Y X,Y X,Y中的一个,往后面加一个字符使得 X X X还是 A A A的子串, Y Y Y还是 B B B的子串.
1 ≤ ∣ A ∣ , ∣ B ∣ ≤ 1 0 5 1\leq|A|,|B|\leq 10^5 1A,B105.

考虑直接对 A , B A,B A,B都建立SAM,由于是要求它们的子串胜负,所以考虑对于SAM上的每个状态求出它的胜负,利用SG函数即可.整个游戏的胜负可以通过SG定理来合并.

值得注意的是,很容易发现SAM上任意一个状态的 s g sg sg值都不会超过字符集大小,因为SAM上每个状态最多连字符集大小条出边.

得到了每一个状态的胜负之后,我们考虑在一个SAM上如何找到第 k k k小的必胜子串,这个问题类似于第 k k k小子串问题,只是我们预处理每一个状态开始的所有子串的时候要忽略必败态,也就是忽略 s g = 0 sg=0 sg=0时候的情况.

两个串的时候,我们可以考虑先确定答案在第一个SAM上的位置,再确定第二个.不过在第一个SAM上跑的时候,要直接减掉可以与正在遍历的状态组成必胜态的子串数量,也就是说 s g = ̸ 0 sg=\not{}0 sg=0,这个时候我们就需要记录 s g sg sg为每一个数时的子串数量了.

时间复杂度 O ( ( ∣ A ∣ + ∣ B ∣ ) Σ 2 ) O((|A|+|B|)\Sigma^2) O((A+B)Σ2).

代码如下:

#include<bits/stdc++.h> 
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=100000,C=26;

struct SAM{
   
  struct automaton{
    int s[C],len,par;
  }tr[N*2+9];
  int cn,last,sg[N*2+9],flag[N*2+9][C+1];
  LL cnt[N*2+9][C+1],sum[N*2+9];
  
  void extend(int x){
    int p=last,np=++cn;
    tr[np].len=tr[p].len+1;
    last=np;
    while (p&&!tr[p].s[x]) tr[p].s[x]=np,p=tr[p].par;
    if (!p) tr[np].par=1;
    else {
      int q=tr[p].s[x];
      if (tr[p].len+1==tr[q].len) tr[np].par=q;
  	  else {
  	    tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
  	    tr[np].par=tr[q].par=cn;
        while (tr[p].s[x]==q&&p) tr[p].s[x]=cn,p=tr[p].par;
      }
    }
  }
  
  int get_sg(int k){
  	if (sg[k]^-1) return sg[k];
  	for (int i=0;i<=C;++i) flag[k][i]=0;
  	for (int i=0;i<C;++i)
  	  if (tr[k].s[i]){
		flag[k][get_sg(tr[k].s[i])]=1;
		for (int j=0;j<=C;++j)
		  cnt[k][j]+=cnt[tr[k].s[i]][j];
	  }
  	int i=0;
  	while (flag[k][i]) ++i;
  	sg[k]=i;
    ++cnt[k][sg[k]];
    for (int i=0;i<=C;++i) sum[k]+=cnt[k][i];
    return sg[k];
  }
  
  void build(char *s,int len){
    last=cn=1;
    for (int i=1;i<=len;++i)
      extend(s[i]-'a');
    for (int i=1;i<=cn;++i) sg[i]=-1;
    get_sg(1);
  }
  
}A,B;

char a[N+9],b[N+9],sa[N+9],sb[N+9];
int n,m,now;
LL k;

LL calc(int a,int b){
  LL sum=0;
  for (int i=0;i<=C;++i)
    sum+=A.cnt[a][i]*(B.sum[b]-B.cnt[b][i]);
  return sum;
}

int dfsa(int x,int pos){
  LL sum1=B.sum[1]-B.cnt[1][A.sg[x]],sum2;
  if (sum1>=k) return x;
  else k-=sum1;
  for (int i=0;i<C;++i)
    if (A.tr[x].s[i]){
      sum2=calc(A.tr[x].s[i],1);
      if (sum2<k) k-=sum2;
      else {
      	sa[pos]='a'+i;
      	return dfsa(A.tr[x].s[i],pos+1);
	  }
	}
  return -1;
}

int dfsb(int x,int pos){
  k-=A.sg[now]!=B.sg[x];
  if (k==0) return x;
  LL sum;
  for (int i=0;i<C;++i)
    if (B.tr[x].s[i]){
      sum=B.sum[B.tr[x].s[i]]-B.cnt[B.tr[x].s[i]][A.sg[now]];
      if (sum<k) k-=sum;
      else {
        sb[pos]='a'+i;
        return dfsb(B.tr[x].s[i],pos+1);
	  }
	}
   return -1;
}

Abigail into(){
  scanf("%lld",&k);
  scanf("%s",a+1);
  n=strlen(a+1);
  A.build(a,n);
  scanf("%s",b+1);
  m=strlen(b+1);
  B.build(b,m);
}

Abigail work(){
  now=dfsa(1,1);
  if (now^-1) dfsb(1,1);
}

Abigail outo(){
  if (now==-1) puts("NO");
  else printf("%s\n%s\n",sa+1,sb+1);
}

int main(){
  into();
  work();
  outo();
  return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值