【ybtoj】【单调队列】写博客

题意

小泽发了一篇博客,由 n 个小写英文字母组成,由于包含违禁词,被自动隐藏。
具体地,违禁词有 m 个,分别为 T1,T2,…,Tm 。
小泽发现,只要博客中,连续地包含了其中违禁词,那么博客就会被自动隐藏。换言之,对于任意 1≤i≤m , Ti 都不能是最终发表的博客 S 的子串。
于是小泽决定在原来的博客 S 上把一部分字母替换成空格,使得它不再包含违禁词。如果她把第 i 个字母替换成空格,与之相邻的两个字母将不会连续,但是整篇博客的价值会减少 ai 。
小泽想要知道,如何替换可以得到一篇不会被自动隐藏的博客,而价值的减少量最少。请你帮她回答这个问题。
1≤n=|S|≤2×105 , 1≤m≤10 , 1≤|Ti|≤2×105 , 0≤ai≤1000.

题解

综合性强的一道好题。
根据数据范围可知 dp 数组应该只有一维状态,那么只能设 \(dp_i\) 表示处理好前 \(i\) 的字母减少的最小价值,而且第 \(i\) 个位置必须改为空格。
可以想到先预处理出违禁串在原串出现的位置,这个可以用 KMP 解决。
把每一个违禁串的位置记为 \([l,r]\),记录 \(pre_r=l\).
那么对于每一个 \(dp_i\),有 \(dp_i\)=\(dp_j\)+\(a_i\).
考虑 \(j\) 的取值范围,是要将 \(i-1\) 之前的位置处理好,那么最后一个需要覆盖的区间就是 \([pre_{i-1},i-1]\)
\(pre_i\) 虽然实际意义上不一定单调不降,但是实际上要维护单调不降,才能转移 dp,同时也能进行单调队列优化。
初始的时候队列里要加入一个“0”元素,保证从“0”转移的情况。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 2e5+10;
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c>='0'&&c<='9')) ch=c,c=getchar();
	while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
int n,m,nxt[N],pre[N];
char s[N],c[11][N];
int a[N];
int dp[N],q[N],l=1,r;

void pretreat(int op)
{
	int j=0,len=strlen(c[op]+1);
	for(int i=1;i<len;i++) 
	{
		while(j&&c[op][j+1]!=c[op][i+1]) j=nxt[j];
		if(c[op][j+1]==c[op][i+1]) j++;
		nxt[i+1]=j;
	}
}
void kmp(int op)
{
	int j=0,len=strlen(s+1),lenc=strlen(c[op]+1);
	for(int i=0;i<len;i++)
	{
		pre[i+1]=max(pre[i],pre[i+1]);
		while(j&&c[op][j+1]!=s[i+1]) j=nxt[j];
		if(c[op][j+1]==s[i+1]) j++;
		if(j==lenc) pre[i+1]=max(pre[i+1],i+1-lenc+1),j=nxt[j];
		//我吐了,lenc打成len...调了好久 
		//注意细节,pre[i+1]的位置刚好是一个违规词 
	}
}
int main()
{
	n=read(),m=read();
	scanf("%s",s+1);
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++)
	{
		scanf("%s",c[i]+1);
		pretreat(i);
		kmp(i);
	}
	n++;
	l=1,r=0;
	for(int i=0;i<=n;i++)
	{
		while(l<=r&&q[l]<pre[i-1]) l++;
		dp[i]=dp[q[l]]+a[i];
		while(l<=r&&dp[q[r]]>=dp[i]) r--;
		q[++r]=i;
	}
	printf("%d",dp[n]);
	return 0;
}
posted @ 2021-09-16 17:10  conprour  阅读(80)  评论(0编辑  收藏  举报