P3181 [HAOI2016]找相同字符 [SAM]

传送门

对第一个串建一个SAM, 拿第二个上去跑

考虑跑到一个点的贡献, 就是(当前匹配的长度 nowlen - right 集合的最短长度 minlen) * right 集合的大小

因为跟每个长度在这之间的串都可以匹配 siz 次

然后跳 link, 对于它上方的点的贡献, 就是 (maxlen - minlen + 1) * siz

所以我们记录每个点被考虑的次数, 最后向前统计一遍即可


#include<bits/stdc++.h>
#define N 400050
using namespace std;
struct Node{
	int ch[26], link, len, siz;
}t[N];
int n, last, tot, c[N], A[N], cnt[N], sum[N]; char s[N];
typedef long long ll; ll ans;
void Extend(int c){
	int cur = ++tot, p = last; 
	t[cur].len = t[p].len + 1; t[cur].siz = 1;
	for(;p && !t[p].ch[c]; p = t[p].link) t[p].ch[c] = cur;
	if(!p) t[cur].link = 1;
	else{
		int q = t[p].ch[c];
		if(t[q].len == t[p].len + 1) t[cur].link = q;
		else{
			int clone = ++tot;
			t[clone].len = t[p].len + 1;
			t[clone].link = t[q].link;
			for(int i=0; i<26; i++) t[clone].ch[i] = t[q].ch[i];
			for(;p && t[p].ch[c] == q; p = t[p].link) t[p].ch[c] = clone;
			t[q].link = t[cur].link = clone;
		}
	} last = cur;
}
int main(){
	scanf("%s", s+1); n = strlen(s+1);
	last = tot = 1;
	for(int i=1; i<=n; i++) Extend(s[i] - 'a');
	for(int i=1; i<=tot; i++) c[t[i].len]++;
	for(int i=1; i<=n; i++) c[i] += c[i-1];
	for(int i=1; i<=tot; i++) A[c[t[i].len]--] = i;
	for(int i=tot; i>=1; i--) t[t[A[i]].link].siz += t[A[i]].siz;
	scanf("%s", s+1); n = strlen(s+1);
	int pos = 1, nowlen = 0;
	for(int i=1; i<=n; i++){
		int c = s[i] - 'a';
		if(t[pos].ch[c]) pos = t[pos].ch[c], nowlen++;
		else{
			for(;pos && !t[pos].ch[c]; pos = t[pos].link);
			if(!pos) pos = 1, nowlen = 0;
			else nowlen = t[pos].len + 1, pos = t[pos].ch[c];
		}
		ans += 1ll * (nowlen - t[t[pos].link].len) * t[pos].siz; cnt[pos]++; sum[pos]++; 
	}
	for(int i=tot; i>1; i--){
		int p = A[i]; sum[t[p].link] += sum[p]; 
		ans += 1ll * (t[p].len - t[t[p].link].len) * t[p].siz * (sum[p] - cnt[p]); 
	} printf("%lld", ans); return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值