您的位置:首页 > 其它

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

2017-08-17 18:58 465 查看

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
;
int l1,l2,len;
int cnta
,cntb
,a
,b[N<<1];
int sa
,rank
,tsa
;
int st
[LogN];
ll suma
,sumb
,height
;
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: