• 魔法 [线段树优化DP]


    也许更好的阅读体验

    (mathcal{Description})

    (D) 正在研究魔法。
    (D) 得到了远古时期的魔法咒语 (S),这个咒语共有 (n) 个音节,每个音节都可以
    抽象为一个小写英文字母。
    但是很快小 (D) 发现这个咒语并不能直接说出——它具有一定的危险性。
    (D) 进行了一些仔细的研究,很快发现危险来源于 (m) 个禁忌词 (T_1 , T_2 , ldots, T_m)
    (D) 发现,只要说出的咒语中,连续地包含了其中某个禁忌词,那么就会带来很
    大的危险。换言之,对于任意 (1 ≤ i ≤ m),(T_i) 都不能是最终说出的咒语 (S') 的子串。
    于是小 (D) 决定在原来的咒语 (S) 上做出一定的删减,使得它不再包含任何禁忌词。
    (D) 发现如果他跳过咒语中第 (i) 个音节,那么咒语的威力会减少 (a_i)
    (D) 想要知道,如何跳过音节可以得到一个安全的咒语,而威力的减少量最少。
    值得一提的是,如果小 (D) 跳过了某个音节,那么与之相邻两个音节也不会变得连续。
    但是小 (D) 并不会,请你帮帮他。

    (mathcal{Solution})

    先用所有的(T)(S)(KMP),得到若干个区间
    那么我们的问题就转化成了有(n)个点和若干个区间,每个点有点权,你要选若干个点使得每个区间内都至少包含一个点,问最小点权和是多少

    考虑(DP)
    先将区间按照(r)升序排序
    (f_{i,j})表示使前(i)个区间都合法,最后一个点为(j)的最小点权和是多少
    那么对一个区间([l_i,r_i]),其有效的(f)的区间也为([l_i,r_i])
    考虑(f_{i,j})肯定是由(f_{i-1,k} kin [l_{i-1},r_{i-1}])转移来的
    于是我们可以去掉一维

    (f_i)表示最后一个点为(i)时的答案
    然后考虑一个一个的加区间

    如图,

    s.png
    后一个区间的蓝色部分的(f)值与前一个区间应是一样的
    而红色区间的值则由绿色区间的值转移过来,设红色区间的一个值为(f_k),则有(f_k=min{f_j,jin[绿色]}+a_k)
    我们可以用线段树查询绿色区间的最小值,并维护红色区间的增值

    (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
    "),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
    ",query(l[id[tot]],r[id[tot]]));
    	return 0;
    }
    

    如有哪里讲得不是很明白或是有错误,欢迎指正
    如您喜欢的话不妨点个赞收藏一下吧

  • 相关阅读:
    DAY09(for语句)
    Python 下载 tushare 数据,然后调用 C++ DLL 计算 wMA 存入本地 csv 文件再 python 读取
    Python socket编程之八:阶段性总结
    Ctex + Lyx = 自用 Latex 编辑环境
    matlab下二重积分的蒙特卡洛算法
    实验9 根据材料编程
    实验5 编写、调试具有多个段的程序
    实验4 [bx]和loop的使用
    实验3 编程、编译、连接、跟踪
    汇编语言第三章总结
  • 原文地址:https://www.cnblogs.com/Morning-Glory/p/11837416.html
Copyright © 2020-2023  润新知