BZOJ4566 [HAOI2016]找相同字符(后缀数组)
文章目录
【注意】最后更新于 February 8, 2020,文中内容可能已过时,请谨慎使用。
题目链接
题意简述
给你两个字符串,从中各取一个子串使这两个子串相同,求方案数。
简要做法
以某两个位置开头的相同子串数=这两个位置开头的后缀的 $lcp$
如果在同一个字符串中,求出 height
数组后使用单调栈求出每个位置作为最小值的贡献即可(单调栈部分与 P2659 美丽的序列,[ZJOI2007]棋盘制作 等题类似,在此就不赘述了;求两两 $lcp$ 之和这部分与 [AHOI2013]差异 类似,故没有写那题的题解)。
由于有两个字符串不太方便,考虑将它们拼接起来并在中间加上一个不存在的字符(如#
)。这样求出拼接后的字符串的答案,减去两个原串的答案,就是最终的答案。
参考代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=400010;
typedef long long ll;
int sa[N],sa2[N<<1],rk[N<<1],px[N],cnt[N],sta[N],top,f[N],height[N];
struct Suffix_Array
{
char s[N];
ll calc()
{
ll out=0;
int n,i,k,w,p,m=200;
n=strlen(s+1);
memset(sa2,0,sizeof(sa2));
memset(cnt,0,sizeof(cnt));
for (i=1;i<=n;++i) ++cnt[rk[i]=s[i]];
for (i=1;i<=m;++i) cnt[i]+=cnt[i-1];
for (i=n;i>=1;--i) sa[cnt[rk[i]]--]=i;
for (w=1;w<n;w<<=1,m=p)
{
memset(cnt,0,sizeof(cnt));
for (p=0,i=n;i>n-w;--i) sa2[++p]=i;
for (i=1;i<=n;++i) if (sa[i]>w) sa2[++p]=sa[i]-w;
for (i=1;i<=n;++i) ++cnt[px[i]=rk[sa2[i]]];
for (i=1;i<=m;++i) cnt[i]+=cnt[i-1];
for (i=n;i>=1;--i) sa[cnt[px[i]]--]=sa2[i];
swap(rk,sa2);
for (p=0,i=1;i<=n;++i) rk[sa[i]]=sa2[sa[i]]==sa2[sa[i-1]]&&sa2[sa[i]+w]==sa2[sa[i-1]+w]?p:++p;
}
for (i=1,k=0;i<=n;++i)
{
if (k) --k;
while (s[i+k]==s[sa[rk[i]-1]+k]) ++k;
height[rk[i]]=k;
}
for (i=1;i<=n;++i)
{
while (top&&height[sta[top]]>=height[i]) --top;
f[i]=i-sta[top];
sta[++top]=i;
}
top=0;
sta[++top]=n+1;
height[n+1]=0;
for (i=n;i>=1;--i)
{
while (top&&height[sta[top]]>height[i]) --top;
out+=(ll)f[i]*(sta[top]-i)*height[i];
sta[++top]=i;
}
return out;
}
} a,b,ab;
int main()
{
int n,m,i;
scanf("%s%s",a.s+1,b.s+1);
n=strlen(a.s+1);
m=strlen(b.s+1);
for (i=1;i<=n;++i) ab.s[i]=a.s[i];
ab.s[n+1]='#';
for (i=1;i<=m;++i) ab.s[n+1+i]=b.s[i];
cout<<ab.calc()-a.calc()-b.calc();
return 0;
}
评论正在加载中...如果评论较长时间无法加载,你可以 搜索对应的 issue 或者 新建一个 issue 。