魔法 [線段樹優化DP]

也許更好的閱讀體驗

Description\mathcal{Description}

DD 正在研究魔法。
DD 得到了遠古時期的魔法咒語 SS,這個咒語共有 nn 個音節,每個音節都可以
抽象爲一個小寫英文字母。
但是很快小 DD 發現這個咒語並不能直接說出——它具有一定的危險性。
DD 進行了一些仔細的研究,很快發現危險來源於 mm 個禁忌詞 T1,T2,,TmT_1 , T_2 , \ldots, T_m
DD 發現,只要說出的咒語中,連續地包含了其中某個禁忌詞,那麼就會帶來很
大的危險。換言之,對於任意 1im1 ≤ i ≤ m,TiT_i 都不能是最終說出的咒語 SS' 的子串。
於是小 DD 決定在原來的咒語 SS 上做出一定的刪減,使得它不再包含任何禁忌詞。
DD 發現如果他跳過咒語中第 ii 個音節,那麼咒語的威力會減少 aia_i
DD 想要知道,如何跳過音節可以得到一個安全的咒語,而威力的減少量最少。
值得一提的是,如果小 DD 跳過了某個音節,那麼與之相鄰兩個音節也不會變得連續。
但是小 DD 並不會,請你幫幫他。

Solution\mathcal{Solution}

先用所有的TTSSKMPKMP,得到若干個區間
那麼我們的問題就轉化成了有nn個點和若干個區間,每個點有點權,你要選若干個點使得每個區間內都至少包含一個點,問最小點權和是多少

考慮DPDP
先將區間按照rr升序排序
fi,jf_{i,j}表示使前ii個區間都合法,最後一個點爲jj的最小點權和是多少
那麼對一個區間[li,ri][l_i,r_i],其有效的ff的區間也爲[li,ri][l_i,r_i]
考慮fi,jf_{i,j}肯定是由fi1,k k[li1,ri1]f_{i-1,k}\ k\in [l_{i-1},r_{i-1}]轉移來的
於是我們可以去掉一維

fif_i表示最後一個點爲ii時的答案
然後考慮一個一個的加區間

如圖,

s.png
後一個區間的藍色部分的ff值與前一個區間應是一樣的
而紅色區間的值則由綠色區間的值轉移過來,設紅色區間的一個值爲fkf_k,則有fk=min{fj,j[]}+akf_k=\min\{f_j,j\in[綠色]\}+a_k
我們可以用線段樹查詢綠色區間的最小值,並維護紅色區間的增值

Code\mathcal{Code}

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年11月11日 星期一 08時24分38秒
*******************************/
#include <cstdio>
#include <fstream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 200005;
const int maxm = 2000006;
const int inf = 0x7f7f7f7f;
//cin 省掉了快讀
int n,m,tot;
int nxt[maxn],l[maxm],r[maxm],w[maxn],id[maxn];
char s[maxn],t[maxn];
bool cmp (int x,int y){	return r[x]^r[y]?r[x]<r[y]:l[x]<l[y];}
//{{{Get
void Get (char *s,int len)
{
	int j=1,k=0;
	reset(nxt);
	while (j<=len){
		if (!k||s[j]==s[k])	nxt[++j]=++k;
		else	k=nxt[k];
	}
}
//}}}
//{{{Match
int Match (char *s,char *t)//s appears in t
{
	int len=strlen(s+1);
	Get(s,len);
	int j=1,k=1,ans=0;
	while (k<=n){
		if (!j||s[j]==t[k])	++j,++k;
		else	j=nxt[j];
		if (j==len+1){
			l[++tot]=k-len,r[tot]=k-1,id[tot]=tot;
			j=nxt[j];
		}
	}
	return ans;
}
//}}}
//{{{SegmentTree
namespace SegmentTree
{
	#define cl k<<1
	#define cr k<<1|1
	#define lm (lt[k]+rt[k])/2
	#define rm (lt[k]+rt[k])/2+1
	const int maxt = 1000006;
	int lt[maxt],rt[maxt],val[maxt],lazy[maxt],tag[maxt],sum[maxt];
	//{{{build
	void build (int l,int r,int k=1)
	{
		lt[k]=l,rt[k]=r,val[k]=inf;
		if (l==r)	return void(sum[k]=w[l]);
		build(l,lm,cl);
		build(rm,r,cr);
		sum[k]=min(sum[cl],sum[cr]);
	}
	//}}}
	//{{{pushdownl
	void pushdownl (int k)
	{
		val[cl]=lazy[k]*sum[cl],val[cr]=lazy[k]*sum[cr];
		lazy[cl]+=lazy[k],lazy[cr]+=lazy[k];
		lazy[k]=0;
	}
	//}}}
	//{{{pushdownt
	void pushdownt (int k)
	{
		tag[cl]+=tag[k],tag[cr]+=tag[k];
		val[cl]+=tag[k],val[cr]+=tag[k];
		tag[k]=0;
	}
	//}}}
	//{{{modify
	void modify (int l,int r,int v,int k=1)
	{
		if (lt[k]>=l&&rt[k]<=r){
			val[k]=sum[k]+v;
			++lazy[k],tag[k]+=v;
			return;
		}
		if (lazy[k])	pushdownl(k);
		if (tag[k])	pushdownt(k);
		if (l<=lm)	modify(l,r,v,cl);
		if (r>=rm)	modify(l,r,v,cr);
		val[k]=min(val[cl],val[cr]);
	}
	//}}}
	//{{{query
	int query (int l,int r,int k=1)
	{
		if (lt[k]>=l&&rt[k]<=r)	return val[k];
		int res=inf;
		if (lazy[k])	pushdownl(k);
		if (tag[k])	pushdownt(k);
		if (l<=lm)	res=min(res,query(l,r,cl));
		if (r>=rm)	res=min(res,query(l,r,cr));
		return res;
	}
	//}}}
}
using namespace SegmentTree;
//}}}
int main()
{
	scanf("%d%d",&n,&m);
	scanf("%s",s+1);
	for (int i=1;i<=n;++i)	cin>>w[i];
	for (int i=1;i<=m;++i){
		scanf("%s",t+1);
		Match(t,s);
	}
	sort(id+1,id+tot+1,cmp);
	int cnt=0;
	for (int i=1;i<=tot;++i){
		int cur=id[i];
		while (i+1<=tot&&l[id[i+1]]==l[cur]&&r[id[i+1]]==r[cur])	++i;
		id[++cnt]=cur;
	}
	tot=cnt;

	if (!tot)	return printf("0\n"),0;

	build(1,n);
	modify(l[id[1]],r[id[1]],0);

	for (int i=2;i<=tot;++i){
		int tl=l[id[i-1]],tr=r[id[i-1]];
		int lt=l[id[i]],rt=r[id[i]];
		int tmp=query(tl,tr);
		l[id[i]]=max(lt,tl);
		
		if (lt>tr)	modify(lt,rt,tmp);
		else if (rt>tr)	modify(tr+1,rt,tmp);
	}
	printf("%d\n",query(l[id[tot]],r[id[tot]]));
	return 0;
}

如有哪裏講得不是很明白或是有錯誤,歡迎指正
如您喜歡的話不妨點個贊收藏一下吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章