广义SAM总结

**~~

广义SAM总结

**~~
性质同普通SAM,相对普通单串SAM而言,广义SAM用于处理多串的问题。即插入多个串构建SAM
具体有在线离线两种方式。
离线:多串建Trie,在Trie上bfs插入节点
模板

struct Trie{
	int root,siz;
	int nxt[MAX_N][26],fa[MAX_N],ch[MAX_N];//ch[i]节点i对应的字符
	void insert(char *s)
	{
		int len=strlen(s+1),now=root;
		repi(i,1,len){
			int c=s[i]-'a';
			if(!nxt[now][c]) nxt[now][c]=++siz,fa[siz]=now,ch[siz]=c;
			now=nxt[now][c];
		}
	}
	void init()
	{
		root=siz=1;
		ms(nxt[root]);
	}
}; 
struct SAM{
	Trie tr;
	int root,siz;
	int nxt[MAX_N<<1][26],fa[MAX_N<<1],ml[MAX_N<<1],pos[MAX_N<<1];//pos[i] trie上节点i在SAM上对应节点编号
	queue<int> q;
	void init()
	{
		root=siz=1;
		ml[root]=fa[root]=0;
		ms(nxt[root]);
	}
	int add(int c,int last)
	{
		int p=last,np=++siz; ms(nxt[np]);
		last=np,ml[np]=ml[p]+1;
		for(;p&&!nxt[p][c];p=fa[p]) nxt[p][c]=np;
		if(!p) fa[np]=root;
		else{
			int q=nxt[p][c];
			if(ml[p]+1==ml[q]) fa[np]=q;
			else{
				int nq=++siz;
				ml[nq]=ml[p]+1;
				memcpy(nxt[nq],nxt[q],sizeof(nxt[q]));
				fa[nq]=fa[q],fa[q]=fa[np]=nq;
				for(;p&&nxt[p][c]==q;p=fa[p]) nxt[p][c]=nq;
			}
		}
		return np;
	}
	void build()
	{
		init();
		repi(i,0,25)if(tr.nxt[tr.root][i]) q.push(tr.nxt[tr.root][i]);
		pos[tr.root]=root;
		while(!q.empty()){
			int now=q.front(); q.pop();
			pos[now]=add(tr.ch[now],pos[tr.fa[now]]);
			repi(i,0,25)if(tr.nxt[now][i]) q.push(tr.nxt[now][i]);
		}
	}
}sam;
/*
sam.tr.init();
repi(i,1,n) ss(s+1),sam.tr.insert(s); 
sam.build();
*/

在线:每次插入新串时从root开始,并额外加一些判断。

struct SAM{
	int root,siz;
	int nxt[MAX_N<<1][26],fa[MAX_N<<1],ml[MAX_N<<1];
	void init()
	{
		root=siz=1;
		ml[root]=fa[root]=0;
		ms(nxt[root]);
	}
	int add(int c,int last)
	{
		if(nxt[last][c]&&ml[last]+1==ml[nxt[last][c]]) return nxt[last][c];
		int p=last,np=++siz,nq; ms(nxt[np]);
		ml[np]=ml[p]+1;
		bool flag=false;
		for(;p&&!nxt[p][c];p=fa[p]) nxt[p][c]=np;
		if(!p) fa[np]=root;
		else{
			int q=nxt[p][c];
			if(ml[p]+1==ml[q]) fa[np]=q;
			else{
				if(ml[p]+1==ml[np]) flag=true;
				nq=++siz,ml[nq]=ml[p]+1;
				memcpy(nxt[nq],nxt[q],sizeof(nxt[q]));
				fa[nq]=fa[q],fa[q]=fa[np]=nq;
				for(;p&&nxt[p][c]==q;p=fa[p]) nxt[p][c]=nq;
			}
		}
		return flag?nq:np;
	}
	void insert(char *s)
	{
		int last=root,len=strlen(s+1);
		repi(i,1,len) last=add(s[i]-'a',last);
	}
}sam;

对与处理Right集合方面,在线处理能处理一些离线不能解决的问题。一般来说离线能解决在线肯定也能,另外显然在线的空间花费少于离线。所以我要离线有什么用

对于维护Right集合,有两种常见的处理模式。
一种是对多个串每个单独/整体串 维护一个Right。如下

//以下为对每个单独维护的情况
struct SAM{
	int Right[MAX_N<<1][2],cnt[MAX_N<<1],id[MAX_N<<1];//Right第二维根据加了多少串来看,后续toposort等位置也要相应作出改动
	int add(int c,int last,int id)
	{
		if(nxt[last][c]&&ml[last]+1==ml[nxt[last][c]]){
			Right[nxt[last][c]][id]=1; return nxt[last][c];//若对多个串同时维护Right,此处为++
		} 
		int p=last,np=++siz,nq; ms(nxt[np]);
		ml[np]=ml[p]+1,Right[np][id]=0;
		bool flag=false;
		for(;p&&!nxt[p][c];p=fa[p]) nxt[p][c]=np;
		if(!p) fa[np]=root;
		else{
			int q=nxt[p][c];
			if(ml[p]+1==ml[q]) fa[np]=q;
			else{
				if(ml[p]+1==ml[np]) flag=true;
				nq=++siz,ml[nq]=ml[p]+1,Right[nq][id]=0;
				memcpy(nxt[nq],nxt[q],sizeof(nxt[q]));
				fa[nq]=fa[q],fa[q]=fa[np]=nq;
				for(;p&&nxt[p][c]==q;p=fa[p]) nxt[p][c]=nq;
			}
		}
		Right[flag?nq:np][id]=1;
		return flag?nq:np;
	}
	void insert(char *s,int id)
	{
		int last=root,len=strlen(s+1);
		repi(i,1,len) last=add(s[i]-'a',last,id);
	}
	void toposort()
	{
		int ML=0;
		repi(i,1,siz) cnt[i]=0;
		repi(i,1,siz) cnt[ml[i]]++,ML=max(ML,ml[i]);
		repi(i,1,ML) cnt[i]+=cnt[i-1];
		repi(i,1,siz) id[cnt[ml[i]]--]=i;
		repd(i,siz,1)repi(j,0,1) Right[fa[id[i]]][j]+=Right[id[i]][j];
	}
}

另一种是插入多个串,Right维护该节点在几个串中出现过

int add(int c,int last,int id)
{
	if(nxt[last][c]&&ml[last]+1==ml[nxt[last][c]]){
		int tmp=nxt[last][c];
		while(tmp&&vis[tmp]!=id) vis[tmp]=id,Right[tmp]++,tmp=fa[tmp];
		return nxt[last][c];
	} 
	int p=last,np=++siz,nq; ms(nxt[np]);
	ml[np]=ml[p]+1;
	bool flag=false;
	for(;p&&!nxt[p][c];p=fa[p]) nxt[p][c]=np;
	if(!p) fa[np]=root;
	else{
		int q=nxt[p][c];
		if(ml[p]+1==ml[q]) fa[np]=q;
		else{
			if(ml[p]+1==ml[np]) flag=true;
			nq=++siz,ml[nq]=ml[p]+1;
			vis[nq]=vis[q],Right[nq]=Right[q];//新建节点继承子点状态
			memcpy(nxt[nq],nxt[q],sizeof(nxt[q]));
			fa[nq]=fa[q],fa[q]=fa[np]=nq;
			for(;p&&nxt[p][c]==q;p=fa[p]) nxt[p][c]=nq;
		}
	}
	int tmp=(flag?nq:np);
	while(tmp&&vis[tmp]!=id) vis[tmp]=id,Right[tmp]++,tmp=fa[tmp];
	return flag?nq:np;
}

例题:
1.P6139 给定n个小写字母组成的串,求本质不同子串个数
模板题 建广义SAM,利用性质对每个节点统计即可

ll cal()
{
	ll ans=0;
	repi(i,1,siz) ans+=ml[i]-ml[fa[i]];
	return ans;
}

2.P3181 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。
对两个串建广义SAM,分别维护一个Right集合,对每个结点统计即可。即该节点串类在第一个串的出现次数 * 第二个串的次数 * 串类代表的串数

	void toposort()
	{
		int ML=0;
		repi(i,1,siz) cnt[i]=0;
		repi(i,1,siz) cnt[ml[i]]++,ML=max(ML,ml[i]);
		repi(i,1,ML) cnt[i]+=cnt[i-1];
		repi(i,1,siz) id[cnt[ml[i]]--]=i;
		repd(i,siz,1)repi(j,0,1) Right[fa[id[i]]][j]+=Right[id[i]][j];
	}
	ll cal()
	{
		ll ans=0;
		repi(i,1,siz) ans+=1ll*Right[i][0]*Right[i][1]*(ml[i]-ml[fa[i]]);
		return ans;
	}

3.P3346 一棵树,不定根,每个节点上有一个字符,求任意两个节点加上其路径构成的本质不串数(叶子节点少于20)
枚举根插入串,再对每个节点统计即可

struct SAM{
~~~
	ll cal()
	{
		ll res=0;
		repi(i,1,siz) res+=1ll*(ml[i]-ml[fa[i]]);
		return res;
	}
}sam;
void dfs(int s,int fa,int last)
{
	last=sam.add(c[s],last);
	reps(s)if(e[i].to!=fa) dfs(e[i].to,s,last);
}
int main()
{ 
	si(n),si(cc);
	init(n);
	repi(i,1,n) si(c[i]);
	repi(i,1,n-1){
		int u,v; si(u),si(v);
		add_edge(u,v),add_edge(v,u);
		in[u]++,in[v]++;
	}
	sam.init();
	repi(i,1,n)if(in[i]==1) dfs(i,0,sam.root);
	printf("%lld\n",sam.cal());
	return 0;
}

4.2019ICPC徐州现场赛-L
给定一棵树,定根。每个节点有一个字符。询问从节点x向上l个字符到达y,y-x路径构成一个串,问有几个节点也可以构成相同串(x本身也计入)
首先对整体建SAM,记录树上每个节点加入后对应SAM中的节点,维护整体Right集合。对每次查询,首先跳到查询的结尾点在SAM上对应的结尾点,后沿fa向上跳到ml[fa]小于要求的匹配长度,不能跳的时候所在的节点的Right大小即为所求。

struct SAM{
	void toposort()
	{
		int ML=0;
		repi(i,1,siz) cnt[i]=0;
		repi(i,1,siz) cnt[ml[i]]++,ML=max(ML,ml[i]);
		repi(i,1,ML) cnt[i]+=cnt[i-1];
		repi(i,1,siz) id[cnt[ml[i]]--]=i;
		repd(i,siz,1) Right[fa[id[i]]]+=Right[id[i]];
	}
	int cal(int pos,int l)
	{
		while(ml[fa[pos]]>=l) pos=fa[pos];
		return Right[pos];
	}
}sam;
int pos[MAX_N];
void dfs(int s,int last)
{
	pos[s]=last=sam.add(c[s],last);
	reps(s) dfs(e[i].to,last);
}
int n,q;
char s[MAX_N];
int main()
{ 
	si(n),si(q),ss(s+1);
	init(n);
	repi(i,1,n) c[i]=s[i]-'A';
	repi(i,2,n){
		int fa; si(fa);
		add_edge(fa,i);
	}
	sam.init(),dfs(1,sam.root);
	sam.toposort();
	while(q--)
	{
		int x,l; si(x),si(l);
		printf("%d\n",sam.cal(pos[x],l));
	}
	return 0;
}

5.bzoj2780 有n个大串和m个询问,每次给出一个字符串s询问在多少个大串中出现过
对每个节点Right记录该集合对应串在几个给定大串中出现过(上面的第二种Right)
每次插入单独维护,并更新到Right中,对查询串在SAM上走到最后返回对应节点的Right大小即可

void insert(char *s,int id)
{
	int last=root,len=strlen(s+1);
	repi(i,1,len) last=add(s[i]-'a',last,id);
}
int cal(char *s)
{
	int now=root,len=strlen(s+1);
	repi(i,1,len){
		int c=s[i]-'a';
		if(nxt[now][c]) now=nxt[now][c];
		else return 0;
	}
	return Right[now];
}

6.bzoj3277 现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串(注意包括本身)
所有子串-该串的所有前缀的所有后缀,按拓扑序把每个节点权值更新,父传子,对每个串每个前缀贡献求和即可

struct SAM{
	ll w[MAX_N<<1]; 	
	void toposort()
	{
		int ML=0;
		repi(i,1,siz) cnt[i]=0,w[i]=1ll*((Right[i]>=k)?(ml[i]-ml[fa[i]]):0);
		repi(i,1,siz) cnt[ml[i]]++,ML=max(ML,ml[i]);
		repi(i,1,ML) cnt[i]+=cnt[i-1];
		repi(i,1,siz) id[cnt[ml[i]]--]=i;
		repi(i,1,siz) w[id[i]]+=w[fa[id[i]]];
	}
	ll cal(char *s,int id)
	{
		ll ans=0;
		int len=strlen(s+1),now=root;
		repi(i,1,len){
			int c=s[i]-'a';
			now=nxt[now][c];
			ans+=w[now];
		}
		return ans;
	}
}sam;
char s[MAX_N<<4];
int st[MAX_N];
int main()
{
	sam.init();
	si(n),si(k);
	st[1]=0;
	repi(i,1,n){
		ss(s+st[i]+1),sam.insert(s+st[i],i);
		st[i+1]=strlen(s+st[i]+1)+2+st[i];
	} 
	sam.toposort();
	repi(i,1,n) printf("%lld%c",sam.cal(s+st[i],i),ce(i,n));
	return 0;
}

暂时到这,后续做完再补充一些套数据结构的题

开始补作业

发布了6 篇原创文章 · 获赞 1 · 访问量 100

猜你喜欢

转载自blog.csdn.net/qq_44684888/article/details/105451905
SAM
今日推荐