洛谷 P4248 [AHOI2013]差异 & P3181 [HAOI2016]找相同字符

链接

P4248 P3181


把这两道题放在一起是要讨论这样一个问题

对于一个已知 \(ht\) 的 \(S\),\(O(n)\) 求 \(\sum\limits_{1\le i<j\le n} lcp(i,j)\)

首先我们可以把上式写成 \(\sum\limits_{1\le i\le j}\sum\limits_{1\le j<i}lcp(i,j)\),也就是对于排序后的后缀的一个位置,我们只考虑排名在它之前的后缀与它的lcp。那么我们依次地考虑每个后缀,发现我们实际需要做的是维护一个单调升序列,于是我们只需要用单调栈维护一下 \(ht\) 值和位置就好了。

int sta[N],pos[N],sum[N],now,ans;
inline int solve(){//\sum_{1\le i<j\le n}lcp(i,j) 
	pos[0]=1,ans=0,now=0;
	for(int i=2;i<=n;i++){
		while(now&&ht[i]<=sta[now])now--;
		sta[++now]=ht[i],pos[now]=i,
		sum[now]=sum[now-1]+ht[i]*(pos[now]-pos[now-1]),
		ans+=sum[now];
	}
	return ans;
}

好了回到这两道题。


分析

P4248 [AHOI2013]差异

我们把题目的式子稍微化化,就是
\((n-1)\times\frac{n\times (n+1)}2-2\sum\limits_{1\le i<j\le n} lcp(i,j)\)
直接上。

P3181 [HAOI2016]找相同字符

我们发现发现这题要求的仍然是上面类似的东西,只是子串的来源被分到了两个字符串,这种情况,我们经常用一个特殊字符将两式连接起来一起处理。这样做了后我们发现会算多一部分,这是从某一个串选了两个子串的答案,所以我们只用把两个子串分别处理的结果减掉就好了。

代码

P4248 [AHOI2013]差异
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){p=p*10+c-'0';c=getchar();}
	return p*f;
}
const int N=5e5+5;
int n,m,rk[N<<1],sa[N],ht[N];
struct llmmkk{
	int fi,se,ref;
}st[N<<1],tmp[N<<1];
int cnt[N];
inline void jsort(){
	for(int i=0;i<=n;i++)cnt[i]=0;
	for(int i=1;i<=n;i++)cnt[st[i].se]++;
	for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>0;i--)tmp[cnt[st[i].se]]=st[i],cnt[st[i].se]--;	
	for(int i=0;i<=n;i++)cnt[i]=0;
	for(int i=1;i<=n;i++)cnt[tmp[i].fi]++;
	for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>0;i--)st[cnt[tmp[i].fi]]=tmp[i],cnt[tmp[i].fi]--;
}
int ston[257];
inline void SA(string S){
	n=S.length();
	for(int i=0;i<n;i++)ston[signed(S[i])]+=(!ston[signed(S[i])]);
	for(int i=1;i<=256;i++)ston[i]+=ston[i-1];	
	for(int i=1;i<=n;i++)rk[i]=ston[signed(S[i-1])];
	for(int k=1,t;k<=(n<<1);k<<=1){
		for(int i=1;i<=n;i++)
			st[i].fi=rk[i],st[i].se=rk[i+k],st[i].ref=i;	
		jsort();t=0;
		for(int i=1;i<=n;i++){
			if((st[i].fi^st[i-1].fi)||(st[i].se^st[i-1].se))t++;
			rk[st[i].ref]=t;			
		}if(t==n)break;
	}
	for(int i=1;i<=n;i++)sa[rk[i]]=i;
	for(int i=1;i<=n;i++){
		ht[rk[i]]=max(ht[rk[i-1]]-1,0ll);
		while(S[sa[rk[i]-1]+ht[rk[i]]-1]==S[i+ht[rk[i]]-1])
			ht[rk[i]]++;
	}
}
string S;
int sta[N],pos[N],sum[N],now,ans;
inline int solve(){
	pos[0]=1,ans=0,now=0;
	for(int i=2;i<=n;i++){
		while(now&&ht[i]<=sta[now])now--;
		sta[++now]=ht[i],pos[now]=i,
		sum[now]=sum[now-1]+ht[i]*(pos[now]-pos[now-1]),
		ans+=sum[now];
	}
	return ans;
}
signed main(){
	cin>>S;SA(S);
	ans=(n+1)*n/2*(n-1)-2*solve();
	cout<<ans;
	return 0;
}
P3181 [HAOI2016]找相同字符
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){p=p*10+c-'0';c=getchar();}
	return p*f;
}
const int N=4e5+5;
int n,rk[N<<1],sa[N],ht[N];
struct llmmkk{
	int fi,se,ref;
}st[N<<1],tmp[N<<1];
int cnt[N];
inline void jsort(){
	for(int i=0;i<=n;i++)cnt[i]=0;
	for(int i=1;i<=n;i++)cnt[st[i].se]++;
	for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>0;i--)tmp[cnt[st[i].se]]=st[i],cnt[st[i].se]--;	
	for(int i=0;i<=n;i++)cnt[i]=0;
	for(int i=1;i<=n;i++)cnt[tmp[i].fi]++;
	for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>0;i--)st[cnt[tmp[i].fi]]=tmp[i],cnt[tmp[i].fi]--;
}
int ston[257];
inline void SA(string S){
	n=S.length();memset(ston,0,sizeof(ston));
	for(int i=0;i<n;i++)ston[signed(S[i])]+=(!ston[signed(S[i])]);
	for(int i=1;i<=256;i++)ston[i]+=ston[i-1];	
	for(int i=1;i<=n;i++)rk[i]=ston[signed(S[i-1])];
	for(int k=1,t;k<=(n<<1);k<<=1){
		for(int i=1;i<=n;i++)
			st[i].fi=rk[i],st[i].se=rk[i+k],st[i].ref=i;	
		jsort();t=0;
		for(int i=1;i<=n;i++){
			if((st[i].fi^st[i-1].fi)||(st[i].se^st[i-1].se))t++;
			rk[st[i].ref]=t;			
		}if(t==n)break;
	}
	for(int i=1;i<=n;i++)sa[rk[i]]=i;
	for(int i=1;i<=n;i++){
		ht[rk[i]]=max(ht[rk[i-1]]-1,0ll);
		while(S[sa[rk[i]-1]+ht[rk[i]]-1]==S[i+ht[rk[i]]-1])
			ht[rk[i]]++;
	}
}
string S,T;
int sta[N],pos[N],sum[N],now,ans;
inline int solve(){//\sum_{1\le i<j\le n}lcp(i,j) 
	pos[0]=1,ans=0,now=0;
	for(int i=2;i<=n;i++){
		while(now&&ht[i]<=sta[now])now--;
		sta[++now]=ht[i],pos[now]=i,
		sum[now]=sum[now-1]+ht[i]*(pos[now]-pos[now-1]),
		ans+=sum[now];
	}
	return ans;
}
int tans=0;
signed main(){
	cin>>S>>T;
	SA(S),tans-=solve();
	SA(T),tans-=solve();
	SA(S+'#'+T),tans+=solve();
	cout<<tans;	
	return 0;
}
上一篇:[Leecode刷题]2. 两数相加


下一篇:python小题目+快速排序(青春是丛林,是荒原,是阳光炙热的奔跑,是大雨滂沱的伫立。)