牛客竞赛字符串专题 NC237308 本质不同公共子串个数(SAM)

文章介绍了一种利用后缀自动机(SuffixAutomaton)解决给定两个字符串s和t时,找出它们公共本质不同子串数量的方法。通过建立s的后缀自动机,遍历t并在自动机上运行,动态维护每个状态的最大公共子串长度,最后进行拓扑排序更新答案。代码示例使用C++实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
在这里插入图片描述

题意:

给出两个串 s、t,求公共本质不同子串的个数。

思路:

对 s 建后缀自动机,将 t 放到上面跑,联系之前学过的用 SAM 求两个串的公共子串,对于 SAM 中的每个状态 p 求出在字符串 t 和 状态 p 中都出现过的最长子串长度 now[p](求完还需要 dfs 拓扑更新一遍)

类比于 P4070 [SDOI2016]生成魔咒,不过我们只关注 SAM 中两个串的 “公共节点”,当 dfs 遍历到某个 “公共节点”,我们加上对应的贡献,具体细节详见代码。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 5e5 + 10, M = N << 1;
int ch[M][26], len[M], fa[M], np = 1, tot = 1;
vector<int> g[M];
char s[N], t[N];
int now[M];	//表示当前在 t  状态 p 中都出现过的最长子串长度,s 的子串都被划分到了每一个状态 p 
long long ans;	//记录答案

void extend(int c)
{
	int p = np; np = ++tot;
	len[np] = len[p] + 1;
	while (p && !ch[p][c]) {
		ch[p][c] = np;
		p = fa[p];
	}
	if (!p) {
		fa[np] = 1;
	}
	else {
		int q = ch[p][c];
		if (len[q] == len[p] + 1) {
			fa[np] = q;
		}
		else {
			int nq = ++tot;
			len[nq] = len[p] + 1;
			fa[nq] = fa[q], fa[q] = fa[np] = nq;
			while (p && ch[p][c] == q) {
				ch[p][c] = nq;
				p = fa[p];
			}
			memcpy(ch[nq], ch[q], sizeof ch[q]);
		}
	}
}

void dfs(int u)
{
	for (auto son : g[u]) {
		dfs(son);
		now[u] = max(now[u], now[son]);	//拓扑更新
	}
	if (now[u]) {	//只加 now[u] 不为 0 的,如果为 0 表示这个点的所有子串都不是两者公共的子串
		//原串建成的 SAM 中的节点 u 表示的最大子串长度 len[u]  now[u] 大小关系不确定,我们应当取 min(len[u], now[u]),保证节点 u 能够包含该长度子串
		ans += min(len[u], now[u]) - len[fa[u]];	//加上对答案造成的贡献
	}
}

signed main()
{
	scanf("%s%s", s, t);
	for (int i = 0; s[i]; ++i) {
		extend(s[i] - 'a');
	}
	for (int i = 2; i <= tot; ++i) {
		g[fa[i]].emplace_back(i);
	}
	//跟之前求两串公共子串的步骤一样
	int p = 1, leng = 0;
	for (int i = 0; t[i]; ++i) {
		int c = t[i] - 'a';
		while (p > 1 && !ch[p][c]) {
			p = fa[p];
			leng = len[p];
		}
		if (ch[p][c]) {
			++leng;
			p = ch[p][c];
		}
		now[p] = max(now[p], leng);
	}
	dfs(1);	//拓扑更新一遍 now 数组

	printf("%lld\n", ans);

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值