广义 SAM 学习笔记

首先,如何手造一个广义 SAM

本质上就是建多串后缀树(后缀树还是蛮好建的吧……

广义 SAM 主要解决多模式串问题(统计一些奇奇怪怪的问题

记号

\(O(|\Sigma|)\) 为字符集大小

\(G(T) = O(\sum len)=O(|T|)\)

(其中,\(G(T)\)\(Trie\)\(T\) 所有叶节点深度之和,\(|T|\)\(Trie\) 树大小)

在线构造

在线算法指,不建出 \(Trie\) 树即可求出广义 \(SAM\)

实现也非常简单,只需要在单串插入建 \(SAM\) 的基础上加几个特判,并且在新插入一个串的时候将 \(last = 1\) 即可。

inline void add(int x)
	{
		int p=last,q,now,k,ch=1;
		if(q=son[x][p]) //特判有转移函数的时候考虑直接拆分节点,避免空节点干扰 
		{
			if(len[p] + 1==len[q]) return (void)(last = q);//如果转移函数的目标恰好与我们想要的一致 ,直接返回即可 
			len[k=++cnt]=len[p]+1,link[k] = link[q],link[q] = last =k;//如果不一致考虑直接拆分出我们想要的 
			for(;ch<=26;son[ch][k]=son[ch][q],ch++);
			for(;p&&!(son[x][p]^q);son[x][p]=k,p=link[p]);
			return;
		}
		last=now=++cnt,len[now] = len[p] + 1;
		for(;p&&!son[x][p];son[x][p]=now,p=link[p]);
		if(!p) return (void)(link[now]=1);
		if(len[p]+1==len[q=son[x][p]]) return (void)(link[now]=q);
		len[k=++cnt] = len[p] + 1,link[k] = link[q],link[q] = link[now] = k;
		for(;ch<=26;son[ch][k]=son[ch][q],ch++);
		for(;p&&!(son[x][p]^q);son[x][p]=k,p=link[p]);
	}

解释一下特判的含义

特判1:

if(q=son[x][p])

原转移函数是否存在,如果存在进一步处理,若不存在则按单串插入规则插入。

特判2:
if(q=son[x][p] && len[p] + 1 != len[q])

在原转移函数存在的情况下,并且处于一个节点的压缩状态,则把这个节点分裂开,按照单串插入中的分裂操作进行即可。返回时,last 为分裂出的节点。

若不处于压缩态,而是表达态,\(last\) 记为 \(q\) 即可。

正确性证明:

首先要知道为什么不在新建节点 \(now\) 然后连边处理。易知,若满足特判一且 len[p] + 1 == len[q] 时,\(q\) 这个节点与我们即将要插入的这个节点是等价的,为了保证子串信息不被分散,应该把这两个节点合并,所以直接返回 \(last = q\)

若满足特判二,则说明 \(q\) 这个节点中有一个节点和我们当前要插入的节点是完全等价的。但是它现在处于压缩状态,我们需要分裂这个节点,让压缩态变成表达态。然而,若是分裂之后,不就又相当于特判一的情况了吗?所以我们在当前情况下不需要新建这个节点,只需要分裂 \(q\) ,返回 \(last\) 为分裂出的节点。


复杂度结论:(请牢牢记清)

状态数为线性 \(O(2|T|)\)

转移函数上界为 \(O(|T||\Sigma|)\)

在线算法时间复杂度 \(O(|T||A|+G(T))\)

实际来说,在线算法比离线快……(这可太离谱了,离线算法还有什么存在意义???

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

板子,不多 bb

#include<bits/stdc++.h>

using namespace std;

#define Int unsigned long long
#define INF 1ll<<60

template<typename _T>
inline void read(_T &x)
{
	x=0;char s=getchar();int f=1;
	while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	x*=f;
}

#define lowbit(x) (x&(-x))
#define gb(x) ((x-1)/T + 1)
#define gl(x) ((x-1)*T + 1)
#define pb push_back
#define fi first
#define sd second
#define Re register
#define MOD(x) (x = (x + mod)%mod)
#define pii pair<int,int>
#define inrange(L,R) (L<=l&&r<=R)
#define outofrange(L,R) (r < L||R < l)
#define sto set<node>::iterator

const int np = 3e6 + 5;
const int nnp = 2e3 + 5;
const int mod = 1e9 + 7;

namespace SAM{
	int len[np];
	int last = 1,cnt = 1;
	int son[28][np];
	int link[np];
	inline void add(int x)
	{
		int p=last,q,now,k,ch=1;
		if(q=son[x][p]) //特判有转移函数的时候考虑直接拆分节点,避免空节点干扰 
		{
			if(len[p] + 1==len[q]) return (void)(last = q);//如果转移函数的目标恰好与我们想要的一致 ,直接返回即可 
			len[k=++cnt]=len[p]+1,link[k] = link[q],link[q] = last =k;//如果不一致考虑直接拆分出我们想要的 
			for(;ch<=26;son[ch][k]=son[ch][q],ch++);
			for(;p&&!(son[x][p]^q);son[x][p]=k,p=link[p]);
			return;
		}
		last=now=++cnt,len[now] = len[p] + 1;
		for(;p&&!son[x][p];son[x][p]=now,p=link[p]);
		if(!p) return (void)(link[now]=1);
		if(len[p]+1==len[q=son[x][p]]) return (void)(link[now]=q);
		len[k=++cnt] = len[p] + 1,link[k] = link[q],link[q] = link[now] = k;
		for(;ch<=26;son[ch][k]=son[ch][q],ch++);
		for(;p&&!(son[x][p]^q);son[x][p]=k,p=link[p]);
	}
}

int n;
char c[np];
int bac[np],sa[np];
signed main()
{
	read(n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",c+1);
		int len = strlen(c + 1);
		for(int j=1;j<=len;j++) SAM::add(c[j]-'a'+1);
		SAM::last = 1;
	}
	long long  Ans = 0;
	for(int i=1;i<=SAM::cnt;i++)Ans +=SAM::len[i] - SAM::len[SAM::link[i]];

 	 cout<<Ans;
 }

P3346 [ZJOI2015]诸神眷顾的幻想乡

ajh 放的傻逼题,看到叶子节点很少,直接对叶子节点暴力建广义就行……

#include<bits/stdc++.h>

using namespace std;

#define int long long
#define Int unsigned long long
#define INF 1ll<<60

template<typename _T>
inline void read(_T &x)
{
	x=0;char s=getchar();int f=1;
	while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	x*=f;
}

#define lowbit(x) (x&(-x))
#define gb(x) ((x-1)/T + 1)
#define gl(x) ((x-1)*T + 1)
#define pb push_back
#define fi first
#define sd second
#define Re register
#define MOD(x) (x = (x + mod)%mod)
#define pii pair<int,int>
#define inrange(L,R) (L<=l&&r<=R)
#define outofrange(L,R) (r < L||R < l)
#define sto set<node>::iterator

const int np = 3e6 + 5;

namespace SAM{
	int len[np];
	int link[np];
	int cnt=1,last=1;
	int son[10][np];
	
	inline void add(int x)
	{
		int p=last,q,now,k,ch=0;
		if(q=son[x][p])
		{
			if(len[p]+1==len[q]) return (void)(last=q);
			len[k = ++cnt] = len[p] + 1,link[k] = link[q],link[q] = last = k;
			for(;ch<=9;son[ch][k]=son[ch][q],ch++);
			for(;p&&!(son[x][p]^q);son[x][p]=k,p=link[p]);
			return ;
		}
		now = last = ++cnt,len[now] = len[p] + 1;
		for(;p && !son[x][p];son[x][p]=now,p=link[p]);
		if(!p) return (void)(link[now]=1);
		if(len[p] + 1 == len[q=son[x][p]]) return (void)(link[now] = q);
		len[k = ++cnt] = len[p] + 1,link[k] = link[q],link[q] = link[now] = k;
		for(;ch<=9;son[ch][k]=son[ch][q],ch++);
		for(;p&&!(son[x][p]^q);son[x][p]=k,p=link[p]);
	}
};

int n,c;
int head[np],cx[np],nxt[np * 2],ver[np * 2],tit,top;
int s[np],s_[np],top_;
inline void add(int x,int y)
{
	ver[++tit] = y;
	nxt[tit] = head[x];
	head[x] = tit;
}

inline void dfs(int x,int ff)
{
	int leaf = 1;
	for(int i=head[x],v;i;i=nxt[i])
	{
		v = ver[i];
		if(v == ff) continue;
		leaf=0;
		dfs(v,x);
	}
	if(leaf) s[++top] = x;
}

inline void dfs_(int x,int ff,int la)
{
	int cop = SAM::last;
	SAM::last = la;
	SAM::add(cx[x]);
	la = SAM::last;
	for(int i=head[x],v;i;i=nxt[i])
	{
		v = ver[i];
		if(v == ff) continue;
		dfs_(v,x,la);
	}
	SAM::last = cop;
}

signed main()
{
	read(n);
	read(c);
	
	for(int i=1;i<=n;i++) read(cx[i]);
	
	for(int i=1,u,v;i<n;i++)
	{
		read(u);
		read(v);
		add(u,v);
		add(v,u);
	}
	int num =0 ;
	for(int i=head[1];i;i=nxt[i]) num++;
	if(num==1) s[++top] = 1;
	dfs(1,0);
	for(int i=1;i<=top;i++) dfs_(s[i],0,1);
	
	int Ans = 0;
	for(int i=1;i<=SAM::cnt;i++)
	Ans += SAM::len[i] - SAM::len[SAM::link[i]] ; 
	
	cout<<Ans<<'\n';
 }

其实我本来是不想学广义的,但培训要讲……

行吧,培训的时候找个机会切掉 [NOI2018] 你的名字

posted @ 2021-09-28 21:43  ·Iris  阅读(155)  评论(0编辑  收藏  举报