SAM 的一些用法

SAM 的基操就不总结了,主要记录一下细节容易弄错的用法

1. 全串匹配

简单来说就是把 SAM 当作 ACAM 来用。

假设需要知道匹配串 \(T\) 在文本串 \(S\) 中的匹配情况,则对 \(S\) 建 SAM,枚举 \(T\) 的每一个字符进行匹配的同时维护当前匹配长度 \(len\) 和匹配节点 \(u\)。具体地,当拿到字符 \(c\)

  1. 存在 \(st[u]\rightarrow to[c]\),则 \(len++\)\(u=st[u]\rightarrow to[c]\)

  2. 不存在 \(st[u]\rightarrow to[c]\),则不断跳 \(link\) 直到存在或者到达根节点

  • 如果存在,先令 \(len=st[u]\rightarrow len+1\),然后 \(u=st[u]\rightarrow to[c]\)

  • 如果到了根节点仍然不存在,令 \(len=0\)

代码:

int u=0,len=0;
F(i,0,(int)t.length()-1){
  int c=t[i]-'a';
  	if(st[u].to[c])len++,u=st[u].to[c];
  	else{
  		while(u&&!st[u].to[c])u=st[u].link;
  		len=st[u].len;
  		if(st[u].to[c])len++,u=st[u].to[c];
  	}
	pre[i+1]=len;//pre数组表示t的每一个前缀的最长可匹配后缀
}

2. 区间匹配

初始给定 \(S\),每次给定 \(T_i\)\(l_i\)\(r_i\) 并询问 \(T_i\)\(S[l_i,r_i]\) 的匹配情况。

仍然对 \(S\) 建出 SAM,此时需要额外处理 SAM 每一个节点的 endpos 集合,在 parent tree 上线段树合并即可。

接下来枚举每个 \(T_i\) 的字符,同时维护当前匹配长度 \(len\) 和匹配节点 \(u\)

  1. 若存在 \(st[u]\rightarrow to[c]\) 且存在 \(st[st[u]\rightarrow to[c]]\rightarrow endpos[l_i+len\sim r_i]\) 则进行转移,\(len++\)\(u=st[u]\rightarrow to[c]\) 并退出

  2. 否则不断令 \(len--\) 并进行操作1

  3. 在这个过程中,一旦发现 \(st[st[u]\rightarrow link]\rightarrow len=len\),立即跳 link

  4. 如果直到 \(len=0\) 都没有转移,则推出退出。

区间匹配:P4770 [NOI2018] 你的名字

代码:

  • 维护 endpos 集合:

    struct state{int to[26],link,len,rt;}st[N<<1];
    int sz,lst,tot;
    string s;
    inline void insert(int c){
    	int cur=++sz,p=lst;
    	st[cur].len=st[lst].len+1,lst=cur;
    	while(p!=-1&&!st[p].to[c])st[p].to[c]=cur,p=st[p].link;
    	if(p==-1)return;
    	int q=st[p].to[c];
    	if(st[p].len+1==st[q].len)return st[cur].link=q,void();
    	int clone=++sz;
    	st[clone]=st[q],st[clone].len=st[p].len+1,st[clone].rt=0;
    	while(p!=-1&&st[p].to[c]==q)st[p].to[c]=clone,p=st[p].link;
    	st[cur].link=st[q].link=clone;
    }
    struct node{int l,r;}T[N<<6];
    inline void addsum(int &p,int l,int r,int x){
    	p=++tot;
    	if(l==r)return;
    	if(x<=mid)addsum(T[p].l,l,mid,x);
    	if(x>mid)addsum(T[p].r,mid+1,r,x);
    }
    inline int merge(int x,int y,int l,int r){
    	if(!x||!y)return x|y;
    	if(l==r)return x;
    	int p=++tot;
    	T[p].l=merge(T[x].l,T[y].l,l,mid);
    	T[p].r=merge(T[x].r,T[y].r,mid+1,r);
    	return p;
    }
    inline bool getsum(int p,int l,int r,int L,int R){
    	if(!p)return 0;
    	if(L<=l&&r<=R)return 1;
    	bool res=0;
    	if(L<=mid)res=getsum(T[p].l,l,mid,L,R);
    	if(R>mid)res|=getsum(T[p].r,mid+1,r,L,R);
    	return res;
    }
    vector<int>t[N<<1];
    inline void dfs(int u){for(int v:t[u])dfs(v),st[u].rt=merge(st[u].rt,st[v].rt,1,L);}
    int main(){
    	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    	cin>>s,L=(int)s.length();
    	st[0].link=-1;
    	F(i,0,L-1)insert(s[i]-'a'),addsum(st[lst].rt,1,L,i+1);
    	F(i,1,sz)t[st[i].link].push_back(i);
    	dfs(0);
    }
    
  • 进行匹配

    string t;
    int l,r,pre[N];
    cin>>t>>l>>r;
    int u=0,len=0;
    F(i,0,(int)t.length()-1){
    	int c=t[i]-'a';
    	while(1){
    		if(st[u].to[c]&&getsum(st[st[u].to[c]].rt,1,L,l+len,r)){len++,u=st[u].to[c];break;}
    		if(!len)break;
    		len--;
    		if(len==st[st[u].link].len)u=st[u].link; 
    	}
    	pre[i+1]=len;//pre含义同上
    }
    

3. 使用 SAM 构建后缀树

倒着加入 SAM 即可。

虽然操作简单,但是有的时候后缀树很好用,相当于对所有后缀做 trie 然后进行了极致的压缩。

在后缀树上dp:P4248 [AHOI2013] 差异

4. 广义 SAM

只介绍离线 bfs 方法,还是比较通用的。

先建 trie,注意额外维护每个节点的父亲和父亲到自己的转移字母。

对 trie 进行 bfs 同时建 SAM 注意这里的 lst 要使用父亲节点的 lst,具体看代码。

P6139 【模板】广义后缀自动机(广义 SAM)

int n,tot,pos[N],sz;
string s;
struct trie{int to[26],fa,c;}t[N];
struct state{int to[26],len,link;}st[N<<1];
inline int insert(int c,int lst){
	int cur=++sz,p=lst;
	st[cur].len=st[p].len+1;
	while(p!=-1&&!st[p].to[c])st[p].to[c]=cur,p=st[p].link;
	if(p==-1)return cur;
	int q=st[p].to[c];
	if(st[p].len+1==st[q].len)return st[cur].link=q,cur;
	int clone=++sz;
	st[clone]=st[q],st[clone].len=st[p].len+1;
	while(p!=-1&&st[p].to[c]==q)st[p].to[c]=clone,p=st[p].link;
	return st[cur].link=st[q].link=clone,cur;
}
int main(){
	cin>>n;
	F(i,1,n){
		cin>>s;
		int u=0;
		F(j,0,(int)s.length()-1){
			int c=s[j]-'a',&v=t[u].to[c];
			if(!v)v=++tot,t[v].fa=u,t[v].c=c;
			u=v;
		}
	}
	st[0].link=-1;
    queue<int>q;
	F(i,0,25)if(t[0].to[i])q.push(t[0].to[i]);
	while(q.size()){
		int u=q.front();q.pop();
		pos[u]=insert(t[u].c,pos[t[u].fa]);
		F(i,0,25)if(t[u].to[i])q.push(t[u].to[i]);
	}
}
posted @ 2025-06-18 08:14  linjingxiang  阅读(24)  评论(0)    收藏  举报