P6152 [集训队作业2018] 后缀树节点数

前置知识

SAM,启发式合并,hash

思路

  1. 首先,对于后缀树,我们可以理解为是反着建的SAM,所以可以将SAM翻转后数节点数。(如果你熟练掌握后缀树也可以直接做)。

  2. 然后,我们可以观察建 SAM 的过程,我们出现了两种点,一种是前缀节点,一种是分裂节点。前缀节点就是我们每次添加一个新字符时新建的节点记录的是当时的整个字符串,分裂节点就是我们发现不合法时,将原来节点拆成两个节点时新建的节点。

  3. 我们先思考简单的情况求整个串建出的SAM的节点数。前缀节点相当的好处理,长度是多少就是多少个(在区间上是一样的)。但是分裂节点不同,什么时候会出现分裂节点呢?当一个字符串出现了不只一次,且至少存在两个起点使前一个字符不同(这种字符串显然只有可能是某个等价类里的最长串)。用数学语言来说,一个节点 \(u\) 是裂节点的条件就是 \(\exists x,y \in endpos_u,LCS(s[1,x],s[1,y])=maxlen_u\)\(LCS\) 表示最长公共后缀,这个条件就是前一个字符不同)。SAM的经典应用就是两个串的 \(LCS\) 就是在 parent 树上的 LCA 。所以对于分裂节点的计数就是对于 有贡献的 LCA的计数。

  4. 有上面的结论,就可以考虑一段区间怎么做了,我们发现这可以理解为区间数颜色。这件事我们可以采用支配对的思路做,对树求出 DFS 序,然后记录每个每个节点与前驱和后继所产生的贡献,我们发现这可以理解为区间数颜色,挂到扫描线上快速做。找前驱后继可以直接用 set 启发式合并 ,扫描线用树状数组即可。

  5. 但是这还没有做完,我们理解的前缀节点与分裂节点是不重的,但是我们发现刚才判定分裂节点的条件不是充要的,在区间 SAM 上一个前缀节点的最长串也有可能出现不只一次且有两个起点前一个不同,所以我们会算重。对于这种情况,我们需要容斥掉这类的贡献。我们首先可以暴力枚举每个前缀来判断,但是时间复杂复不优。仔细观察后发现,答案具有可二分性,一个长的前缀满足那么比它短的就满足,所以我们先二分在来判断 \(s[l,mid]\) 是否被算作分裂节点,显然它得是某个 LCA的最长串且在当前区间这个 LCA 被统计了。判断是最长串,我们可以将每个节点的最长串 hash 后存下来,就可以用 map 快速找,是否出现过则可以在扫描线时多记一下每个颜色最靠右的出现位置就可以判断了。

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e5+10,bas=5201314;
struct node
{
	int len,fa;
	map<int,int> ch;
}sam[N];
struct stu
{
	int l,id;
};
vector<stu> di[N],q[N];
vector<int> ve[N];
set<int> st[N];
unordered_map<ll,int> mp; 
int siz[N],son[N],las=1,tot=1,n,m,wz[N],a[N],ans[N],lst[N],sh[N],qz[N];
ll ba[N],s[N];
const ll P = (((ll)1) << 61) - 1;
inline ll Add(ll x, ll y) { return x + y >= P ? x + y - P : x + y; }
inline ll Sub(ll x, ll y) { return x < y ? x + P - y : x - y; }
inline ll Mod(__int128 x) { return Sub((x & P) + (x >> 61), P); } // ?
inline ll Mul(__int128 x, ll y) { return Mod(x * y); } // ?
int lowbit(int x){return x&(-x);}
int query(int x){int res=0;while(x){res+=sh[x];x-=lowbit(x);}return res;}
void modify(int x,int z){while(x<=n){sh[x]+=z;x+=lowbit(x);}return ;} 
void insert(int c)
{
	int p=las,np=las=++tot;
	sam[np].len=sam[p].len+1;
	for(;!sam[p].ch[c]&&p;p=sam[p].fa)sam[p].ch[c]=np;
	if(!p){sam[np].fa=1;return;}
	int q=sam[p].ch[c];
	if(sam[q].len==sam[p].len+1){sam[np].fa=q;return;}
	int nq=++tot;sam[nq]=sam[q];sam[nq].len=sam[p].len+1;
	sam[np].fa=sam[q].fa=nq;
	for(;sam[p].ch[c]==q&&p;p=sam[p].fa)sam[p].ch[c]=nq;
}
void dfs(int u)
{
	siz[u]=1; 
	for(int v:ve[u])
	{
		dfs(v);
		if(wz[v])wz[u]=wz[v];
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;//辅助启发式合并 
	}
}
void dfs1(int u)
{
	if(son[u])
	{
		dfs1(son[u]);
		swap(st[u],st[son[u]]);
	}
	if(qz[u])
	{
		auto nxt=st[u].upper_bound(qz[u]),pre=st[u].lower_bound(qz[u]);
		if(nxt!=st[u].end())di[*nxt].push_back({qz[u]-sam[u].len,u});
		if(pre!=st[u].begin())
		{
			pre--;
			di[qz[u]].push_back({*pre-sam[u].len,u});
		}
		st[u].insert(qz[u]);
	}
	for(int v:ve[u])
	{
		if(v==son[u])continue;
		dfs1(v);
		//迭代器不熟
		set<int>::iterator it; 
		for(it=st[v].begin();it!=st[v].end();it++)
		{
			int z=*it;
			auto nxt=st[u].upper_bound(z),pre=st[u].lower_bound(z);
			if(nxt!=st[u].end())di[*nxt].push_back({z-sam[u].len,u});
			if(pre!=st[u].begin())
			{
				pre--;
				di[z].push_back({*pre-sam[u].len,u});
			}
		}
		for(it=st[v].begin();it!=st[v].end();it++)st[u].insert(*it);
		//st[v].clear();有必要吗? 
	}	
}
ll hs(int l,int r) 
{
	return Sub(s[r],Mul(s[l-1],ba[r-l+1]));
}
bool check(int l,int r)
{
	int z=mp[hs(l,r)];
	return z&&lst[z]>=l;
}
int main()
{
	//freopen("P6152_4.in","r",stdin);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
//	clock_t Begin,End;
//	double du;
//	Begin=clock();
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i],a[i]+=1;
	reverse(a+1,a+n+1);//反串
	ba[0]=1;
	for(int i=1;i<=n;i++)
	{
		ba[i]=Mul(ba[i-1],bas);
		s[i]=Add(Mul(s[i-1],bas),a[i]);
	}
	for(int i=1;i<=n;i++)
	{
		insert(a[i]); 
		qz[las]=wz[las]=i;
	}
	for(int i=2;i<=tot;i++)ve[sam[i].fa].push_back(i);
	dfs(1);
	for(int i=2;i<=tot;i++)mp[hs(wz[i]-sam[i].len+1,wz[i])]=i;
	for(int u:ve[1])dfs1(u);//不要1 
	for(int i=1;i<=m;i++)
	{
		int l,r;cin>>l>>r;
		l=n-l+1,r=n-r+1;
		q[l].push_back({r,i}); 
		ans[i]=l-r+1;
	} //
	for(int i=1;i<=n;i++)
	{
		//cout<<"1"<<'\n';
		for(auto x:di[i])
		{
			if(lst[x.id]>=x.l)continue;
		//	cout<<x.l<<" "<<i<<" "<<x.id<<'\n'; 
			modify(x.l,1);
			if(lst[x.id])modify(lst[x.id],-1);
			lst[x.id]=x.l; 
		} 
		//cout<<"1"<<'\n';
		//cout<<query(n)<<'\n';
		for(auto x:q[i])
		{
			ans[x.id]+=(query(i)-query(x.l-1));
			int l=x.l,r=i,jg=l-1;
			while(l<=r)
			{
				int mid=(l+r)>>1;
				if(check(x.l,mid))l=mid+1,jg=mid;
				else r=mid-1;
			}
			ans[x.id]-=(jg-x.l+1);
		}
	} 
	for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
	return 0;
 } 
posted @ 2025-05-25 20:07  exCat  阅读(17)  评论(1)    收藏  举报