[HAOI2016]找相同字符(广义SAM)

[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());
}
上一篇:BZOJ2882 工艺【SAM】 最小循环串


下一篇:[NOI2018]你的名字