[HAOI2016]找相同字符(广义SAM)
题面
给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。
分析
此题有一个比较繁琐的后缀数组做法,但是用广义SAM可以秒杀。
把两个串建成广义SAM,对于每个后缀,记录\(endpos\)集合中落在第一个串中和第二个串中的位置个数,记为\(cnt_{x,0},cnt_{x,1}\)。
对于自动机上的每个节点\(x\),出现位置方案数的贡献是\(cnt_{x,0} \cdot cnt_{x,1}\),注意这个节点代表了一系列不同子串,还要乘上这个位置代表的本质不同子串个数\(len(x)-len(link(x))\)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define maxn 200000
#define maxc 26
using namespace std;
typedef long long ll;
int n,m;
char s[maxn+5],t[maxn+5];
struct SAM{
#define len(x) (t[x].len)
#define link(x) (t[x].link)
struct node{
int len;
int link;
int ch[maxc+1];
int cnt[2];
}t[maxn*4+5];
const int root=1;
int ptr=1,last=root;
void extend(int c,int type){
int p=last,cur=++ptr;
len(cur)=len(p)+1;
if(type<=1) t[cur].cnt[type]++;
while(p&&!t[p].ch[c]){
t[p].ch[c]=cur;
p=link(p);
}
if(p==0) link(cur)=root;
else{
int q=t[p].ch[c];
if(len(p)+1==len(q)) link(cur)=q;
else{
int clo=++ptr;
link(clo)=link(q);
len(clo)=len(p)+1;
for(int i=0;i<=maxc;i++) t[clo].ch[i]=t[q].ch[i];
link(q)=link(cur)=clo;
while(p&&t[p].ch[c]==q){
t[p].ch[c]=clo;
p=link(p);
}
}
}
last=cur;
}
void topo_sort(){
static int in[maxn*4+5];
queue<int>q;
for(int i=2;i<=ptr;i++) in[link(i)]++;
for(int i=2;i<=ptr;i++) if(!in[i]) q.push(i);
while(!q.empty()){
int x=q.front();
q.pop();
in[link(x)]--;
for(int i=0;i<=1;i++) t[link(x)].cnt[i]+=t[x].cnt[i];
if(in[link(x)]==0) q.push(link(x));
}
}
ll calc(){
ll ans=0;
for(int i=2;i<=ptr;i++) ans+=1ll*(t[i].len-t[link(i)].len)*t[i].cnt[0]*t[i].cnt[1];
return ans;
}
}T;
int main(){
scanf("%s",s+1);
scanf("%s",t+1);
n=strlen(s+1),m=strlen(t+1);
for(int i=1;i<=n;i++) T.extend(s[i]-'a',0);
// T.extend(26,2);
T.last=T.root;
for(int i=1;i<=m;i++) T.extend(t[i]-'a',1);
T.topo_sort();
printf("%lld\n",T.calc());
}