Typesetting math: 100%
欢迎来爆踩我|

AFewSuns

园龄:4年11个月粉丝:42关注:3

线段树优化dp题单&题解

Tips:右边有目录

前言

前置知识:线段树,dp

线段树优化dp是什么呢?

O(n2) 的 dp 用线段树优化到 O(nlogn)

一般做题步骤:

  1. 想如何暴力 dp

  2. 观察转移方程,如何用线段树优化

好了,你已经会了,让我们看题吧。(题目简介自己看原题)

CF115E Linear Kingdom Races

首先想贪心,发现不太行,考虑用 dp 做。

fi 为前 i 场比赛中的答案,发现需要对区间排序,然而会有包含的情况,无法转移。

那就设 fi 为前 i 条道路中的答案。

设目前的比赛为 [l,r],那么我们需要知道这中间的道路有没有被修。

于是设 f[i][j] 为第 i 条道路前,从第 j 条道路开始往后都要修的答案(不修 j

怎么算 f[i][j] 呢?它能被 f[i1][j] 转移得到,然后又能被所有符合条件的比赛区间 [li,ri] 加上 wi 的贡献。

什么是符合条件的呢?当且仅当 j<li,ri=i,因为这些比赛的道路都被修好了,是可行的,并且不能与之前算重。

所以将比赛区间按右端点排序,枚举 i,再枚举以 i 为右端点的区间,它就能给 f[i][0li1] 加上 wi

最后 f[i][i] 就是目前的答案,然后更新全局答案 ans(注意与 0max)。

多完美啊,直到你看到 n 的范围是 2×105


这东西怎么用线段树维护呢?

枚举 i,将线段树上的第 j 个位置看成 f[i][j]

当我将 i 往右移时,先将所有的 f[i][j] 都会减去 ai(修路的代价)

然后还是将比赛区间排序,枚举右端点为 i 的区间。

发现了什么?我们本来要一个一个给 f[i][0li1] 加上 wi,这样是 O(n) 的,但是现在我们可以把它看成区间加,即,将 [0,li1] 加上 wi

用线段树维护它,是 O(logn) 的!

最后把 f[i][i] 修改为 ans 即可。

fr(i,1,n){
	mdf(1,0,n,0,i-1,-a[i]);
	for(ll j=0;j<v[i].size();j++) mdf(1,0,n,0,q[v[i][j]].l-1,q[v[i][j]].w);
	ans=max(ans,tree[1]);
	mdf(1,0,n,i,i,ans);
}

CF474E Pillars

有点类似求 LIS,设 fi 为前 i 个数中,选第 i 个数的答案(最大长度)

然后怎么转移呢?它可以由 fj 转移过来,当且仅当 |aiaj|d,1ji

具体来说,fi=max1ji|aiaj|dfj+1,时间 O(n2),考虑如何优化。

ai 离散化,建一颗维护 ai(值域)的线段树,假设 |aiaj|d 这个式子成立的边界是 liri

什么意思?就是 aj[1,li][ri,n] 里面的 j 都可以转移到 fi(离散化后)

然后就简单了。用线段树维护答案最大值,求 fi 的时候相当于求区间 max,每次在 ai 处更新 fi

fr(i,1,n){
	node res=query(1,1,cnt,1,pl[i])+query(1,1,cnt,pr[i],cnt);
	f[i]=res.maxx+1;
	lst[i]=res.p;
	mdf(1,1,cnt,a[i],i,f[i]);
}

P3431 [POI2005]AUT-The Bus

题目描述(这题翻译过烂)

一个 n×m 的矩阵,k 个点有 pi 个人,一辆大巴车从 (1,1) 走到 (n,m),只能往下或往右,求最多能接多少人。

n,m109,k105


假设 n,m103,我该怎么做?直接对每个点 dp,f[i][j]=max(f[i1][j],f[i][j1])+p[i][j]

n,m109 怎么做?离散化即可。

再来,假设 k103,我该怎么做?

fi=maxjxjxi,yjyifj+pi

直接暴力枚举 k 个点进行转移。

现在考虑怎么优化。

首先对第一维 xi 进行排序,那就只用考虑 yjyi 了。

离散化 yi 后以它建一颗线段树,每次即查 [1,yi] 上的 max

然后再把 fi 更新线段树里的 yi

fr(i,1,k){
	f[i]=query(1,1,cnt,1,a[i].y)+a[i].p;
	mdf(1,1,cnt,a[i].y,f[i]);
}

CF597C Subsequences

先不管数据范围,思考如何暴力 dp。

一个非常非常非常暴力的做法就是设 f[i][j][s] 为前 i 个数中,LIS 长度为 j 且末尾为 s 的子序列个数。

如何转移?分两种情况:

  1. saif[i][j][s]=f[i1][j][s]

  2. s=aif[i][j][s]=f[i1][j][s]+ss=1sf[i1][j1][ss]

还是考虑如何用线段树优化。

发现 k+1 很小,直接暴力开 k+1 颗线段树,然后以 LIS 末尾数字 s 为下标建线段树。

枚举 i,对于第一种情况,不用管;第二种情况,相当于是在原来的基础上加上 ss=1sf[i1][j1][ss]

这个东西直接在 j1 这颗线段树里面查 [1,s] 的和即可。

一点细节:倒序枚举 j,原因如 01 背包,不要影响后面的答案。

注意特判 j=1,这个时候序列个数是 1

fr(i,1,n){
	pfr(j,k+1,1){
		if(j==1) tmp=1;
		else tmp=query(rt[j-1],1,n,1,a[i]);
		mdf(rt[j],1,n,a[i],tmp);
	}
}

Upd:发现没有题解和我一样思路,可以水一篇题解了。

CF833B The Bakery

大概跟上题同样思路,设 f[i][j][s] 为前 i 个数中,最后一段是 [j,i],然后共有 s 段的答案,prei 为上一个 ai 的位置。

得出转移方程:

  1. 1jpreif[i][j][s]=f[i1][j][s]

  2. prei<j<if[i][j][s]=f[i1][j][s]+1

  3. j=if[i][j][s]=f[i1][1j1][s1]+1

然后用线段树优化,考虑到 k 很小,直接暴力开 k 颗线段树,然后以 j 为下标。

枚举 i,再枚举 s,对于第一种情况直接忽略;第二种情况相当于区间加,第三种情况相当于区间查询。

注意倒序枚举 s,当 s=1 的时候只能有 [1,i] 这一段。

fr(i,1,n){
	pfr(s,k,1){
		if(s==1) add(rt[s],1,n,pre[i]+1,min(1ll,i-1));
		else add(rt[s],1,n,pre[i]+1,i-1);
		if(s==1) tmp=0;
		else tmp=query(rt[s-1],1,n,1,i-1);
		mdf(rt[s],1,n,i,tmp+1);
	}
}

CF1304F2 Animal Observation (hard version)

感谢 ExplodingKonjac 的供题。

很明显的 dp,设 f[i][j] 为第 i 天选择以 (i,j) 为左上角的矩形(录像),前 i 天的答案。

然后 f[i][j] 可以由上一行的任意矩形(录像)转移过来,只是重合部分不一样。尽管使用前缀和优化,这样的时间复杂度还是 O(nm2)

可不可以去掉对上一行,每个矩形(录像)都算一次重合部分的操作呢?

假设我已经知道了 f[i][j] 的情况下,我要把矩形往右移一个,也就是 j 加一,会发生什么变化?

f[i1][jk]f[i1][j1] 的矩形重合部分,都减去了 a[i][j]f[i1][j]f[i1][j+k1] 的矩形重合部分,都加了 a[i][j+k1]

然后这个东西用线段树维护即可。

对每一行,暴力求出 f[i][1],然后从左到右算答案。

fr(i,2,n){
	fr(j,1,m-k+1) b[j]=f[i-1][j]-sum[i][k]+sum[i][min(k,j-1)];
	build(1,1,m);
	f[i][1]=tree[1]+cnm[i][1];
	fr(j,2,m-k+1){
		mdf(1,1,m,max(1ll,j-k),j-1,a[i][j-1]);
		mdf(1,1,m,j,min(j+k-1,m),-a[i][j+k-1]);
		f[i][j]=tree[1]+cnm[i][j];
	}
}

CF960F Pathwalks

看到图,看到最长路径,难道是一个图上的 dp?

往这个地方想了一会,发现没什么思路。

重新看清楚题:编号与权值严格递增,编号是输入顺序的编号。

也就是说,我的路径编号一定是从小到大的。换个思路,在这上面 dp,就很简单了。

fi 为前 i 条边中,选第 i 条的答案。

因为第 i 条边是 uivi,我要从某条边转移过来,必须这条边的终点是 ui

所以我要满足这些条件:

  1. j<i

  2. vj=ui

  3. wj<wi

然后 fi=maxfj+1

第一个条件直接按顺序枚举 i 没掉,关键在后两个。

发现第二个貌似好弄一些,于是对每一个点开一颗线段树(当然动态开点),相当于查 ui 这颗线段树。

然后以 wi 为下标,相当于查 ui 这颗线段树内,[0,wi1] 的最小值。

最后记得用 fi 更新 vi 这颗线段树上的 wi 位置。

fr(i,1,m){
   f[i]=query(rt[e[i].u],0,1e5,0,e[i].w-1)+1;
   mdf(rt[e[i].v],0,1e5,e[i].w,f[i]);
}

[USACO12OPEN]Bookshelf G

有个很假的 dp,就是设 fi,j 为前 i 本书中,第 i 本所在架子目前宽度是 j 的答案。

L109,无论时间还是空间都会炸,得想办法弄掉一维。

第一维肯定要保留,于是设 fi 为前 i 本书的答案。考虑怎么枚举原先的第二维。

首先,第 i 本书所在的架子一定是连续的一段书,即 (j,i]。为什么是左开?因为这样会好处理很多。(你也可以试着用闭区间)

然后我们必须满足 wj+1++wiL,这个东西可以用前缀和预处理,即 sumisumjL

再移一下项:sumjsumiL,这个东西在枚举 i 的时候直接 lower_bound 一下就行了。

所以 fi=minj(fj+max{hj+1,,hi})

看,左开的好处体现出来了。

暴力转移 O(n2),已经比之前优秀多了。但能不能继续优化呢?

观察这个式子,最难处理的就是 max{hj+1,,hi}

有个很显然的性质,就是这个东西随着 j 递增而递减。然后又是考虑 i 变成 i+1 的时候会有什么变化。

它能够产生影响,说明 hi 更新了某些 max{hj+1,,hi},即 hihj+1hi1 都要大。

但是当其中一个 hkhi 要大时,hi 就不能产生影响。

所以预处理出离 i 最近的比 hi 大的位置 lsti,所有 lstij<imax{hj+1,,hi} 就是 hi。剩下的不变。

然后用线段树维护,处理一些细节,没了。

fr(i,1,n){
	mdfmax(1,0,n,lst[i],i-1,h[i]);
	ll tmp=lower_bound(sum,sum+n+1,sum[i]-L)-sum;
	f[i]=query(1,0,n,tmp,i-1);
	mdf(1,0,n,i,f[i]);
}

[TJOI2011]书架

跟上题一样,不再赘述。

[NWRRC2015]Journey to the “The World’s Start”

题目描述(这题翻译过烂)

你要从第 1 个点坐地铁到第 n 个点,有 n1 张车票,每一张车票的乘车范围为 ri=i,并且有价格 pi。乘车范围 ri 表示你能从 i 点坐到 [iri,i+ri] 内的点下车(不越界),然后再上车。

在第 i 个点下车的所花时间是 di,第 1n 个点下车不花时间,从一个点坐到另一个点需要 1 单位时间(如果 1 坐到 4,需要 3 单位时间)。你需要在 t 时间内到达 n,且只能用一种车票可以用无数次),求车票费用最小值。

例如样例就是选择了 ri=2 的车票,然后 124


如果要从 1 坐到 n,则无论如何都要 n1 单位时间。所以先让 t 减去 n1

有个很明显的单调性,就是如果我 rk 这个范围的车票可行,那么 [rk,n1] 范围的车票都可行。

所以我只用找出来最小满足条件的 rk,那么 [rk,n1] 都可行,答案便是 mini=kn1pi

这个东西可以用二分找,现在的关键便是如何求第 k 张车票的最短时间。

假设目前用的是第 k 张车票,范围是 rk=k

首先我只能坐地铁到后面的点,不然肯定不优。

然后想办法各种各样的贪心,发现都是错的(有兴趣的可以试一下能不能做)。于是开始想如何 dp。

fi 为在 i 点下车的答案(最小值)。

根据前面的条件, fi=minj=max(1,ik)i1fj+di

然后 fn 即为所求。

如果我们暴力求解,这样时间是 O(n2) 的,带上二分变成了 O(n2logn),会 T 掉。

观察式子,极其简单,只有一个区间求 min

用一个线段树优化一下,就没了。

时间复杂度 O(nlog2n)

bl chk(ll k){
	fr(i,2,n){
		f[i]=query(1,1,n,max(1ll,i-k),i-1)+d[i];
		mdf(1,1,n,i,f[i]);
	}
	return f[n]<=t;
}

Upd:发现此题没题解,去水一篇了。

练习题

我不会告诉你是因为我还没打

[ZJOI2010]基站选址

CF675E Trains and Statistic

CF1557D Ezzat and Grid

P6477 [NOI Online #2 提高组] 子序列问题

本文作者:AFewSuns

本文链接:https://www.cnblogs.com/AFewSuns/p/15470358.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   AFewSuns  阅读(1725)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起