牛客2022 暑期多校3 H Hacker(SAM + 线段树查询区间内部最大子段和)

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

题意:

给定 主串 以及 若干副串副串长度固定每个位置 都有一个 权值,要求在 主串和副串的公共子串中 找到一个 连续区间,使得 连续区间的权值和最大,求 最大权值和

思路:

本题其实就是 这两道板子题:SPOJ 2774 Longest Common SubstringAcWing 245. 你能回答这些问题吗 硬生生的结合版,

SAM 维护 主串枚举的各个副串所有公共子串,对于 主串某个副串某个公共子串所在区间,我们利用 线段树 维护 区间内部最大子段和 即可。

简单题,但是比赛的时候由于没学 SAM 而寄了

不过值得一提的是,代码中的将 主串副串所有公共子串所在区间 提取出来的操作:

int p = 1, t = 0;
for (int j = 1; ss[j]; ++j) {
	int sta, ed;	//存储每个公共子串所在的合法区间左右端点
	int c = ss[j] - 'a';
	while (p > 1 && !ch[p][c]) {	//经典匹配操作
		p = fa[p];
		t = len[p];
	}
	ed = j - 1;	//此时由于当前字符s[j]还未匹配,因此右端点为j-1
	if (ch[p][c]) {	//如果当前字符s[j]匹配成功,则右端点移至j,且LCS的长度t++
		p = ch[p][c];
		++t;
		++ed;
	}
	if (t) {	//当前公共子串长度不为0,即合法,根据其长度t计算左端点sta(因为右端点ed已经求出,相减即可)
		sta = ed - t + 1;
	}
}

时间复杂度:

O ( n l o g n ) O(nlogn) O(nlogn)

代码:

#include <bits/stdc++.h>

using namespace std;
//#define map unordered_map
#define int long long
int n, m, k;
const int N = 1e5 + 10, M = N << 1;
int fa[M], ch[M][26], len[M], cnt[M];
int np = 1, tot = 1;
char s[N], ss[N];
int w[N];
struct node {
	int l, r;
	int tmax, sum, lmax, rmax;
} t[N << 2];

void pushup(node& u, node& l, node& r) {
	u.sum = l.sum + r.sum;
	u.tmax = max(max(l.tmax, r.tmax), l.rmax + r.lmax);
	u.lmax = max(l.lmax, l.sum + r.lmax);
	u.rmax = max(r.rmax, r.sum + l.rmax);
}

void pushup(int u) {
	pushup(t[u], t[u << 1], t[u << 1 | 1]);
}

void build(int u, int l, int r) {
	t[u] = { l, r };
	if (l == r) {
		t[u].tmax = t[u].sum = t[u].lmax = t[u].rmax = w[l];
		return;
	}

	int mid = l + r >> 1;
	build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
	pushup(u);
}

node ask(int u, int l, int r) {
	if (l <= t[u].l && r >= t[u].r) return t[u];

	int mid = t[u].l + t[u].r >> 1;
	if (r <= mid) return ask(u << 1, l, r);
	else if (l >= mid + 1) return ask(u << 1 | 1, l, r);
	else {
		auto left = ask(u << 1, l, r);
		auto right = ask(u << 1 | 1, l, r);
		node res;
		pushup(res, left, right);
		return res;
	}
}

void extend(int c) {
	int p = np;
	np = ++tot;
	len[np] = len[p] + 1, cnt[np] = 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]);
		}
	}
}

signed main()
{
	cin >> n >> m >> k;
	cin >> s + 1;
	for (int i = 1; i <= n; ++i) {
		extend(s[i] - 'a');
	}
	for (int i = 1; i <= m; ++i) {
		scanf("%lld", &w[i]);
	}
	build(1, 1, m);
	for (int i = 0; i < k; ++i) {
		scanf("%s", ss + 1);
		int p = 1, t = 0;
		int res = -2e18;
		for (int j = 1; ss[j]; ++j) {
			int sta, ed;
			int c = ss[j] - 'a';
			while (p > 1 && !ch[p][c]) {
				p = fa[p];
				t = len[p];
			}
			ed = j - 1;
			if (ch[p][c]) {
				p = ch[p][c];
				++t;
				++ed;
			}
			if (t) {
				sta = ed - t + 1;
				res = max(max(res, ask(1, sta, ed).tmax), (int)0);
			}
		}
		printf("%lld\n", res);
	}

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值