【ybtoj高效进阶 21251】撰写博客(KMP)(单调队列优化DP)

撰写博客

题目链接:ybtoj高效进阶 21251

题目大意

给你一个大字符串和一些小字符串,然后大字符串的每个字符有修改的费用,修改可以把它修改成空格。
空格原本不出现在字符串中,然后问你要让所有的小字符串都不是大字符串的子串的最小修改费用。

思路

首先不难想出要找到小字符串在大字符串的出现,可以用 KMP 解决。

然后你考虑 DP,设 f i f_i fi 为搞定前 i i i 个字符(最后一个一定要修改)的最小费用,那你考虑求出一个东西 r i r_i ri 为如果要选字符 i i i,然后前面的字符可以选到哪个位置(我这里是 r i r_i ri 这个位置是不能选的)
然后这个你 KMP 找出来的时候记录一下,然后记得还要前缀最大值一下。

那都前缀最大值了,那它就是递增的。

你再看 f i f_i fi 怎么转移:
f i = a i + min ⁡ r i − 1 ⩽ j < i { f j } f_{i}=a_i+\min\limits_{r_{i-1}\leqslant j<i}\{f_j\} fi=ai+ri1j<imin{fj}
(注意范围是 r i − 1 r_{i-1} ri1 不是 r i r_i ri

然后由于 r i − 1 r_{i-1} ri1 递增可以用单调队列优化 DP。

然后就可以了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

int n, m, a[200005], tn, r[200005];
char s[200005], t[200005];
int f[200005], sta[200005], l;
int fail[200005];

int read() {
	int re = 0;
	char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		re = (re << 3) + (re << 1) + c - '0';
		c = getchar();
	}
	return re;
}

int main() {
//	freopen("wzadx.in", "r", stdin);
//	freopen("wzadx.out", "w", stdout);
	
	n = read(); m = read();
//	scanf("%d %d", &n, &m);
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i++) a[i] = read();
//	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= m; i++) {
		scanf("%s", t + 1);
		tn = strlen(t + 1);
		
		int j = 0;
		for (int k = 2; k <= tn; k++) {//KMP 匹配找到每个子串的出现
			while (j && t[j + 1] != t[k]) j = fail[j];
			if (t[j + 1] == t[k]) j++;
			fail[k] = j;
		}
		j = 0;
		for (int k = 1; k <= n; k++) {
			while (j && t[j + 1] != s[k]) j = fail[j];
			if (t[j + 1] == s[k]) j++;
			if (j == tn) r[k] = max(r[k], k - j + 1);//找到从 k 开始最多往前保留多少个字符
		}
	}
	
	for (int i = 1; i <= n + 1; i++)//前缀一下
		r[i] = max(r[i], r[i - 1]);
	
	sta[++sta[0]] = 0; l = 1;
	for (int i = 1; i <= n + 1; i++) {
		while (l <= sta[0] && sta[l] < r[i - 1]) l++;//单调队列优化
		f[i] = a[i] + f[sta[l]];
		while (l <= sta[0] && f[sta[sta[0]]] >= f[i]) sta[0]--;
		sta[++sta[0]] = i;
	}
	
	printf("%d", f[sta[l]]);
	
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值