洛谷3181 BZOJ4566 HAOI2016 找相同字元 SAM
阿新 • • 發佈:2018-12-07
題意:
給你兩個字串,求這兩個字串有多少對相同的子串,本質相同但位置不同算不同。串長<=2e5
題解:
我們考慮像以往一樣對於第一個串建SAM,讓第二個串在SAM上跑並統計答案,結果我們發現這樣並不能做。。。
於是這個題的做法是把兩個串建成一個SAM,中間用一個特殊字元隔開。兩個串建成一個SAM之後,我們發現可以對SAM形成的DAG拓撲排序,然後從拓撲序大的往拓撲序小的更新答案,實質上應該是parent樹上兒子更新父親,兩種寫法應該是等價的。更新的是一個點在第一個串和第二個串分別的right集合大小,寫法可以看看程式碼。因為parent樹上的子節點表示的字串都包含了父節點表示的字串,子節點的right中的每一個元素也都包含了父節點表示的字串,而在 與 之間的所有字串都不會在parent樹上更接近根的地方再出現,所以就一起在這裡統計答案。所以在一個點那裡對答案的貢獻就是 。答案要開long long。
程式碼:
#include <bits/stdc++.h>
using namespace std;
int n,nn,qwq,fa[1600010],ch[1600010][26],len[1600010],rt=1,lst=1,cnt=1;
int sz[1600010],a[1600010],rk[1600010],cnt1[1600010],cnt2[1600010];
char s[500010],s1[200010];
long long ans;
inline void insert(int x)
{
int cur=++cnt,pre=lst;
lst=cur;
len[cur]=len[pre]+1;
for(;pre&&!ch[pre][x];pre=fa[pre])
ch[pre][x]=cur;
if(!pre)
fa[cur]=rt;
else
{
int ji=ch[pre][x];
if(len[ji]==len[pre]+1)
fa[cur]=ji;
else
{
int gg=++cnt;
memcpy(ch[gg],ch[ji],sizeof(ch[ji]));
len[gg]=len[pre]+1;
fa[gg]=fa[ji];
fa[ji]=fa[cur]=gg;
for(;pre&&ch[pre][x]==ji;pre=fa[pre])
ch[pre][x]=gg;
}
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
qwq=n;
scanf("%s",s1+1);
nn=strlen(s1+1);
s[++n]='#';
for(int i=1;i<=nn;++i)
s[++n]=s1[i];
for(int i=1;i<=n;++i)
insert(s[i]-'a');
for(int i=1;i<=cnt;++i)
a[len[i]]++;
for(int i=1;i<=n;++i)
a[i]+=a[i-1];
for(int i=cnt;i>=1;--i)
rk[a[len[i]]--]=i;
int ji=rt;
for(int i=1;i<=n;++i)
{
int x=s[i]-'a';
ji=ch[ji][x];
if(len[ji]<=qwq+1)
++cnt1[ji];
else
++cnt2[ji];
}
for(int i=cnt;i>=1;--i)
{
cnt1[fa[rk[i]]]+=cnt1[rk[i]];
cnt2[fa[rk[i]]]+=cnt2[rk[i]];
}
for(int i=1;i<=cnt;++i)
ans+=1ll*cnt1[i]*cnt2[i]*(len[i]-len[fa[i]]);
printf("%lld\n",ans);
return 0;
}