BZOJ 4566 [Haoi2016]找相同字符 后缀数组+ST表

Description

给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两
个子串中有一个位置不同。

Input

两行,两个字符串s1,s2,长度分别为n1,n2。1 <=n1, n2<= 200000,字符串中只有小写字母

Output

输出一个整数表示答案

Sample Input

aabb
bbaa

Sample Output

10

HINT




如此简洁的题意就来做做,结果被虐爆了= =
一个子串可以看作是一个后缀的前缀,然后相同的子串个数就是两个不同后缀的lcp。
把这些求和就好了。
这引导我们使用后缀数组。

首先当然是把两个串连起来,中间加一个分隔符;
这个分隔符的ascii可以很小,也可以很大,当然为了空间最好是96或者126.
接着直接求一下sa以及height。
如果我们枚举两个串内的后缀的起点,然后可以O(1)求出它们的lcp,
这样的话就是O(N^2)的。
然而O(N)的没搞出来……看了一位大神的题解觉着有了思路(%)
orz
要求两个后缀的lcp,首先找出它们的rank是x,y,那么它们的lcp就是min(height[x..y])
我们找到了min(height[x..y])的位置z,那么也就是说,
height[x..z]>=height[z]
height[z+1..y]>=height[z]
如果说我们统计出来了x..z有A1个第一个串的后缀,B1个第二个串的后缀,
z+1..y有A2个第一个串的后缀,B2个第二个串的后缀,
那么任意跨越z的两个后缀,它们的lcp就是height[z]
那么答案累计上A1*B2+A2*B1,
然后分别递归计算x..z和z+1..y即可。

对于求z的过程,根据height预处理一个st表就可以了。
注意,height里面是按照rank来的。

一开始竟然以为要高精= =
其实200000算一下是不会爆的……

跑得真的慢……写得贼丑(笑哭)


#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int 
	N=(200005<<1),
	LogN=19;
char s[N];
int l1,l2,len;
int cnta[N],cntb[N],a[N],b[N<<1];
int sa[N],rank[N],tsa[N];
int st[N][LogN];
ll suma[N],sumb[N],height[N];
void Get_SA(){
	for (int i=0;i<=26;i++) cnta[i]=0;
	for (int i=1;i<=len;i++) cnta[s[i]-97]++;
	for (int i=1;i<=26;i++) cnta[i]+=cnta[i-1];
	for (int i=len;i;i--) sa[cnta[s[i]-97]--]=i;
	rank[sa[1]]=1;
	for (int i=2;i<=len;i++)
		rank[sa[i]]=rank[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
	for (int j=1;rank[sa[len]]!=len;j<<=1){
		for (int i=1;i<=len;i++) a[i]=rank[i],b[i]=rank[i+j];
		for (int i=0;i<=len;i++) cnta[i]=cntb[i]=0;
		for (int i=1;i<=len;i++) cnta[a[i]]++,cntb[b[i]]++;
		for (int i=1;i<=len;i++) cnta[i]+=cnta[i-1],cntb[i]+=cntb[i-1];
		for (int i=len;i;i--) tsa[cntb[b[i]]--]=i;
		for (int i=len;i;i--) sa[cnta[a[tsa[i]]]--]=tsa[i];
		rank[sa[1]]=1;
		for (int i=2;i<=len;i++)
			rank[sa[i]]=rank[sa[i-1]]+(a[sa[i]]!=a[sa[i-1]] || b[sa[i]]!=b[sa[i-1]]);
	}
}
void Get_H(){
	ll t=(ll)0;
	for (int i=1;i<=len;i++){
		if (t) t--;
		while (s[i+t]==s[sa[rank[i]-1]+t]) t++;
		height[rank[i]]=t;
	}
}
void PreRMQ(){
	for (int i=1;i<=len;i++)
		st[i][0]=i;
	for (int j=1;j<=LogN;j++)
		for (int i=1;i<=len;i++)
			if (i+(1<<j)-1>len) break;
				else
				if (height[st[i][j-1]]>height[st[i+(1<<(j-1))][j-1]])
					st[i][j]=st[i+(1<<(j-1))][j-1];
						else
					st[i][j]=st[i][j-1];
}
void PreSUM(){
	suma[0]=sumb[0]=(ll)0;
	for (int i=1;i<=len;i++)
	 	suma[i]=suma[i-1]+(ll)(sa[i]<=l1),
	 	sumb[i]=sumb[i-1]+(ll)(sa[i]>l1+1);
}
int RMQ(int x,int y){
	int k=(int)((double)log(y-x+1)/(double)log(2));
	if (height[st[x][k]]<height[st[y-(1<<k)+1][k]])
		return st[x][k]; else
		return st[y-(1<<k)+1][k];
}
ll solve(int L,int R){
	if (L>=R) return (ll)0;
	int mid=RMQ(L+1,R);
	return solve(L,mid-1)+solve(mid,R)+
		height[mid]*((suma[mid-1]-suma[L-1])*(sumb[R]-sumb[mid-1])+
					(sumb[mid-1]-sumb[L-1])*(suma[R]-suma[mid-1]));
}
int main(){
	char s1[N>>1],s2[N>>1];
	scanf("%s",s1+1);
	scanf("%s",s2+1);
	l1=strlen(s1+1),l2=strlen(s2+1);
	
	for (int i=1;i<=l1;i++) s[i]=s1[i];
	s[l1+1]='{';
	for (int i=1;i<=l2;i++) s[l1+1+i]=s2[i];
	len=l1+l2+1;
	
	Get_SA(),Get_H();
	PreRMQ(),PreSUM();
	
	printf("%lld\n",solve(1,len));
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值