P3181 [HAOI2016] 找相同字符 题解
Description
给定两个字符串,求满足“分别从两个字符串中各取出一个子串并且这两个子串相同”的方案数。
Solution
问题转化:对于两个字符串的每一个公共子串,它的贡献为在两个字符串中出现次数的乘积。求所有公共子串贡献之和。
遇到所有子串的问题,SAM 是个好东西。
考虑在两个字符串之间插入一个无关字符然后丢到 SAM 里。这样其中的每一个状态(节点)代表了在两串中 endpos 均相同的一类子串,那么这一类子串的贡献可以统一求出来,即:在 \(s1\) 中出现次数,在 \(s2\) 中出现次数和这一类子串的个数三者的乘积。
至于在两串的出现个数怎么统计?只需要给每个状态节点开两个 cnt,插入第一个串的时候累加在 cnt1 ,第二个串同理。最后 dfs 的时候两个 cnt 都进行字数求和即可。
时间复杂度 \(O(n_1+n_2)\) 。
代码:
#include<bits/stdc++.h>
#define N 400005
#define F(i,j,k) for(int i=j;i<=k;i++)
using namespace std;
struct state{int to[27],link,cnt[3],len;}st[N<<1];
int sz,lst;
long long ans;
string s1,s2;
bool typ;
inline void insert(int c){
int cur=++sz,p=lst;
st[cur].len=st[lst].len+1,st[cur].cnt[typ]=1,lst=cur;
while(p!=-1&&!st[p].to[c])st[p].to[c]=cur,p=st[p].link;
if(p==-1)return;
int q=st[p].to[c];
if(st[p].len+1==st[q].len)return st[cur].link=q,void();
int clone=++sz;
st[clone]=st[q],st[clone].len=st[p].len+1,st[clone].cnt[0]=st[clone].cnt[1]=0;
while(p!=-1&&st[p].to[c]==q)st[p].to[c]=clone,p=st[p].link;
st[cur].link=st[q].link=clone;
}
vector<int>t[N<<1];
inline void dfs(int u){
for(int v:t[u])dfs(v),st[u].cnt[0]+=st[v].cnt[0],st[u].cnt[1]+=st[v].cnt[1];
ans+=1ll*st[u].cnt[0]*st[u].cnt[1]*(st[u].len-st[st[u].link].len);
}
int main(){
st[0].link=-1;
cin>>s1>>s2;
F(i,0,(int)s1.length()-1)insert(s1[i]-'a');
typ=2,insert(26),typ=1;
F(i,0,(int)s2.length()-1)insert(s2[i]-'a');
F(i,1,sz)t[st[i].link].push_back(i);
dfs(0),cout<<ans;
}
至于无关字符,把转移数组开大一位,当作第 \(27\) 个字符来插入即可。
这里为了方便(为了规避掉一些奇怪的错误),又开了一个 cnt3 存无关字符的结束标记(但其实没必要,也可以在 insert 函数特判一下无关字符)。

浙公网安备 33010602011771号