BZOJ 4566 [Haoi2016]找相同字符 后缀数组+ST表
2017-08-17 18:58
465 查看
Description
给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。
Input
两行,两个字符串s1,s2,长度分别为n1,n2。1 <=n1, n2<= 200000,字符串中只有小写字母Output
输出一个整数表示答案Sample Input
aabbbbaa
Sample Output
10HINT
传送门如此简洁的题意就来做做,结果被虐爆了= =
一个子串可以看作是一个后缀的前缀,然后相同的子串个数就是两个不同后缀的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; }
相关文章推荐
- ●BZOJ 4566 [Haoi2016]找相同字符
- Bzoj4566 [Haoi2016]找相同字符
- BZOJ 4566 [Haoi2016]找相同字符 ——广义后缀自动机
- 【bzoj4566】[Haoi2016]找相同字符【后缀自动机】
- [后缀自动机][树形DP] BZOJ 4566: [Haoi2016]找相同字符
- BZOJ4566 [Haoi2016]找相同字符 SAM+拓扑
- BZOJ 4566: [Haoi2016]找相同字符(后缀数组的两种做法)
- [Bzoj4566][Haoi2016]找相同字符(广义后缀自动机)
- [BZOJ4566][HAOI2016]找相同字符
- [BZOJ4566][HAOI2016]找相同字符(SAM)
- bzoj 4566: [Haoi2016]找相同字符
- BZOJ4566 [Haoi2016]找相同字符
- ●BZOJ 4566 [Haoi2016]找相同字符
- bzoj4566 [Haoi2016]找相同字符
- BZOJ 4566: [Haoi2016]找相同字符 后缀自动机
- bzoj 4566: [Haoi2016]找相同字符 后缀自动机
- BZOJ 4566: [Haoi2016]找相同字符
- bzoj 4566: [Haoi2016]找相同字符【SAM上DP
- 【bzoj4566】[Haoi2016]找相同字符
- BZOJ 4566: [Haoi2016]找相同字符