【WOJ2856】Mushroom 的字符(后缀数组)(链表)

题解:

首先我们知道直接求一个串的本质不同的子串可以用SAM,只需要每次新加入节点之后计算 l e n [ u ] − l e n [ f a ] len[u]-len[fa] len[u]len[fa]加入答案就行了。但是这道题需要删除前面的点,所以用不了这种方法。

还有一种方法就是用SA,求出 h t ht ht数组之后计算每个位置开始的点对答案的贡献就行了。

我们知道一个串子串的 h t ht ht数组是可以从原来的 h t ht ht数组中间抽出来的(利用ST表)。也就是说我们是有办法快速计算任何一个子串的本质不同子串个数的。

离线求出原串的后缀数组,设 l , r l,r l,r表示当前处理的子串的左右端点位置。

我们会发现一个问题,就是加入右端点之后的 h t ht ht数组可能会超出当前右边界。但是观察一下就会发现,实际上 h t ht ht超出右边界端点集合的只可能是当前串的一个后缀,且这个后缀里面所有点的 h t ht ht全部超出右边界,并且这个后缀之前没有任何一个点的 h t ht ht会超出右边界。

由字典序的性质和 h t ht ht数组的定义显然。

而且这些点加入后 h t ht ht超过右端点,说明以这些点为开头的子串全部已经在这之前的其他位置出现过了,这一部分加入后自己内部形成的子串对当前答案没有任何贡献。我们可以维护一下这个无用后缀的位置 t t t

那么答案就很显然了,由于无用后缀之后的串没有任何贡献,我们可以直接不考虑,只需要考虑开头在无用后缀之前的串就行了,不考虑本质不同的话总贡献为一个等差数列求和: ( 2 ∗ r − t − l + 3 ) ∗ ( t − l ) / 2 (2*r-t-l+3)*(t-l)/2 (2rtl+3)(tl)/2,然后去掉重复计算的,也就是 h t ht ht数组之和,减掉就行了。

但是我们发现 l + 1 l+1 l+1会导致原来存在的串消失, r + 1 r+1 r+1会导致可能出现之前没有的小子串,这些情况下原来无用的后缀可能会变得有用,需要处理一下。

所以考虑用链表维护这个子串的 S A SA SA,用set维护当前子串的 r k rk rk集合,新加入一个点的时候找到它在的位置,更新链表和无用后缀之前 h t ht ht数组之和就行了(注意我们不需要把 h t ht ht数组维护出来)。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	char get(){char c;while(isspace(c=gc())&&c!=EOF);return c;}
}
using namespace IO;

using std::cout;
#define cerr cout

cs int mod=1e9+7;
inline int add(int a,int b){return (a+=b)>=mod?a-mod:a;} 
inline int dec(int a,int b){return (a-=b)<0?a+mod:a;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int Inc(int &a,int b){return (a+=b)>=mod&&(a-=mod),a;}
inline int Dec(int &a,int b){return (a-=b)<0&&(a+=mod),a;}

cs int N=1e6+6;

char s[N];int n;
int sa[N],rk[N],ht[N];
int bin[N];

inline void radix_sort(int *x,int *y,int m,int n){
	memset(bin+1,0,sizeof(int)*m);
	for(int re i=1;i<=n;++i)++bin[x[i]];
	for(int re i=1;i<=m;++i)bin[i]+=bin[i-1];
	for(int re i=n;i;--i)sa[bin[x[y[i]]]--]=y[i];
}

inline void build(int n){
	int *x=rk,*y=ht;
	for(int re i=1;i<=n;++i)x[i]=s[i],y[i]=i;
	radix_sort(x,y,128,n);
	int m=128;
	for(int re i=1,cnt=0;cnt<n;i<<=1){
		cnt=0;
		for(int re j=n-i+1;j<=n;++j)y[++cnt]=j;
		for(int re j=1;j<=n;++j)if(sa[j]>i)y[++cnt]=sa[j]-i;
		radix_sort(x,y,m,n);std::swap(x,y);
		x[sa[1]]=1;cnt=1;
		for(int re j=2;j<=n;++j)
		x[sa[j]]=(y[sa[j]]==y[sa[j-1]]&&y[sa[j]+i]==y[sa[j-1]+i])?cnt:++cnt;
		m=cnt;
	}
	for(int re i=1;i<=n;++i)rk[sa[i]]=i;
	for(int re k=0,i=1,j;i<=n;ht[rk[i++]]=k)
	for(k?--k:0,j=sa[rk[i]-1];s[i+k]==s[j+k];++k);
}

int mn[20][N],Log[N];
inline void init_RMQ(){
	Log[0]=-1;
	for(int re i=1;i<=n;++i)mn[0][i]=ht[i],Log[i]=Log[i>>1]+1;
	for(int re i=1;(1<<i)<=n;++i)
	for(int re j=1;j+(1<<i)-1<=n;++j)
	mn[i][j]=std::min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
}

inline int qy(int l,int r){
	if(l>r)std::swap(l,r);
	int t=Log[r-l+1];
	return std::min(mn[t][l],mn[t][r-(1<<t)+1]);
}

inline int lcp(int a,int b){
	a=rk[a],b=rk[b];
	if(a>b)std::swap(a,b);
	return qy(a+1,b);
}

std::set<int> S;
int pre[N],nxt[N];
char q[N];int Q;
signed main(){
//	freopen("mushroom.in","r",stdin);//freopen("mushroom.out","w",stdout);
	scanf("%d",&Q);
	for(int re i=1;i<=Q;++i)if((q[i]=get())=='+')s[++n]=get();
	build(n);init_RMQ();nxt[n+1]=n+1;
	int r=0,l=1,t=1,rep=0,ans=0;
	for(int re i=1;i<=Q;++i){
		switch(q[i]){
			case '+':++r;break;
			case '-':{
				Dec(rep,lcp(l,pre[l]));
				Dec(rep,lcp(l,nxt[l]));
				if(pre[l]<=n&&nxt[l]<=n)Inc(rep,lcp(pre[l],nxt[l]));
				nxt[pre[l]]=nxt[l];
				pre[nxt[l]]=pre[l];
				S.erase(rk[l++]);
				break;
			}
		}
		while(t<=r){
			auto iter=S.lower_bound(rk[t]);
			int p=iter==S.begin()?n+1:sa[*(--iter)];
			pre[t]=p;
			nxt[t]=nxt[p];
			int l1=0,l2=0;
			if(pre[t]<=n&&(l1=lcp(pre[t],t))>=r-t+1)break;
			if(nxt[t]<=n&&(l2=lcp(nxt[t],t))>=r-t+1)break;
			Inc(rep,l1+l2);
			if(pre[t]<=n&&nxt[t]<=n)Dec(rep,lcp(pre[t],nxt[t]));
			pre[nxt[t]]=nxt[pre[t]]=t;
			S.insert(rk[t++]);
		}
		int cur=(ll)(2*r-t-l+3)*(t-l)/2%mod;
		Dec(cur,rep);
		Inc(ans,cur);
	}
	cout<<ans<<"\n";
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值