线段树优化dp题单&题解
Tips:右边有目录
前言
前置知识:线段树,dp
线段树优化dp是什么呢?
把 的 dp 用线段树优化到
一般做题步骤:
-
想如何暴力 dp
-
观察转移方程,如何用线段树优化
好了,你已经会了,让我们看题吧。(题目简介自己看原题)
CF115E Linear Kingdom Races
首先想贪心,发现不太行,考虑用 dp 做。
设 为前 场比赛中的答案,发现需要对区间排序,然而会有包含的情况,无法转移。
那就设 为前 条道路中的答案。
设目前的比赛为 ,那么我们需要知道这中间的道路有没有被修。
于是设 为第 条道路前,从第 条道路开始往后都要修的答案(不修 )
怎么算 呢?它能被 转移得到,然后又能被所有符合条件的比赛区间 加上 的贡献。
什么是符合条件的呢?当且仅当 ,因为这些比赛的道路都被修好了,是可行的,并且不能与之前算重。
所以将比赛区间按右端点排序,枚举 ,再枚举以 为右端点的区间,它就能给 加上 。
最后 就是目前的答案,然后更新全局答案 (注意与 取 )。
多完美啊,直到你看到 的范围是
这东西怎么用线段树维护呢?
枚举 ,将线段树上的第 个位置看成
当我将 往右移时,先将所有的 都会减去 (修路的代价)
然后还是将比赛区间排序,枚举右端点为 的区间。
发现了什么?我们本来要一个一个给 加上 ,这样是 的,但是现在我们可以把它看成区间加,即,将 加上
用线段树维护它,是 的!
最后把 修改为 即可。
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,设 为前 个数中,选第 个数的答案(最大长度)
然后怎么转移呢?它可以由 转移过来,当且仅当
具体来说,,时间 ,考虑如何优化。
将 离散化,建一颗维护 (值域)的线段树,假设 这个式子成立的边界是 与
什么意思?就是 在 或 里面的 都可以转移到 (离散化后)
然后就简单了。用线段树维护答案最大值,求 的时候相当于求区间 ,每次在 处更新
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
题目描述(这题翻译过烂)
一个 的矩阵, 个点有 个人,一辆大巴车从 走到 ,只能往下或往右,求最多能接多少人。
假设 ,我该怎么做?直接对每个点 dp,
那 怎么做?离散化即可。
再来,假设 ,我该怎么做?
直接暴力枚举 个点进行转移。
现在考虑怎么优化。
首先对第一维 进行排序,那就只用考虑 了。
离散化 后以它建一颗线段树,每次即查 上的
然后再把 更新线段树里的
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。
一个非常非常非常暴力的做法就是设 为前 个数中,LIS 长度为 且末尾为 的子序列个数。
如何转移?分两种情况:
-
:
-
:
还是考虑如何用线段树优化。
发现 很小,直接暴力开 颗线段树,然后以 LIS 末尾数字 为下标建线段树。
枚举 ,对于第一种情况,不用管;第二种情况,相当于是在原来的基础上加上
这个东西直接在 这颗线段树里面查 的和即可。
一点细节:倒序枚举 ,原因如 01 背包,不要影响后面的答案。
注意特判 ,这个时候序列个数是 。
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
大概跟上题同样思路,设 为前 个数中,最后一段是 ,然后共有 段的答案, 为上一个 的位置。
得出转移方程:
-
:
-
:
-
:
然后用线段树优化,考虑到 很小,直接暴力开 颗线段树,然后以 为下标。
枚举 ,再枚举 ,对于第一种情况直接忽略;第二种情况相当于区间加,第三种情况相当于区间查询。
注意倒序枚举 ,当 的时候只能有 这一段。
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)
很明显的 dp,设 为第 天选择以 为左上角的矩形(录像),前 天的答案。
然后 可以由上一行的任意矩形(录像)转移过来,只是重合部分不一样。尽管使用前缀和优化,这样的时间复杂度还是
可不可以去掉对上一行,每个矩形(录像)都算一次重合部分的操作呢?
假设我已经知道了 的情况下,我要把矩形往右移一个,也就是 加一,会发生什么变化?
的矩形重合部分,都减去了 。 的矩形重合部分,都加了 。
然后这个东西用线段树维护即可。
对每一行,暴力求出 ,然后从左到右算答案。
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,就很简单了。
设 为前 条边中,选第 条的答案。
因为第 条边是 ,我要从某条边转移过来,必须这条边的终点是 。
所以我要满足这些条件:
然后
第一个条件直接按顺序枚举 没掉,关键在后两个。
发现第二个貌似好弄一些,于是对每一个点开一颗线段树(当然动态开点),相当于查 这颗线段树。
然后以 为下标,相当于查 这颗线段树内, 的最小值。
最后记得用 更新 这颗线段树上的 位置。
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,就是设 为前 本书中,第 本所在架子目前宽度是 的答案。
但 ,无论时间还是空间都会炸,得想办法弄掉一维。
第一维肯定要保留,于是设 为前 本书的答案。考虑怎么枚举原先的第二维。
首先,第 本书所在的架子一定是连续的一段书,即 。为什么是左开?因为这样会好处理很多。(你也可以试着用闭区间)
然后我们必须满足 ,这个东西可以用前缀和预处理,即
再移一下项:,这个东西在枚举 的时候直接 一下就行了。
所以
看,左开的好处体现出来了。
暴力转移 ,已经比之前优秀多了。但能不能继续优化呢?
观察这个式子,最难处理的就是 。
有个很显然的性质,就是这个东西随着 递增而递减。然后又是考虑 变成 的时候会有什么变化。
它能够产生影响,说明 更新了某些 ,即 比 都要大。
但是当其中一个 比 要大时, 就不能产生影响。
所以预处理出离 最近的比 大的位置 ,所有 的 就是 。剩下的不变。
然后用线段树维护,处理一些细节,没了。
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”
题目描述(这题翻译过烂)
你要从第 个点坐地铁到第 个点,有 张车票,每一张车票的乘车范围为 ,并且有价格 。乘车范围 表示你能从 点坐到 内的点下车(不越界),然后再上车。
在第 个点下车的所花时间是 ,第 和 个点下车不花时间,从一个点坐到另一个点需要 单位时间(如果 坐到 ,需要 单位时间)。你需要在 时间内到达 ,且只能用一种车票(可以用无数次),求车票费用最小值。
例如样例就是选择了 的车票,然后
如果要从 坐到 ,则无论如何都要 单位时间。所以先让 减去
有个很明显的单调性,就是如果我 这个范围的车票可行,那么 范围的车票都可行。
所以我只用找出来最小满足条件的 ,那么 都可行,答案便是
这个东西可以用二分找,现在的关键便是如何求第 张车票的最短时间。
假设目前用的是第 张车票,范围是
首先我只能坐地铁到后面的点,不然肯定不优。
然后想办法各种各样的贪心,发现都是错的(有兴趣的可以试一下能不能做)。于是开始想如何 dp。
设 为在 点下车的答案(最小值)。
根据前面的条件,
然后 即为所求。
如果我们暴力求解,这样时间是 的,带上二分变成了 ,会 T 掉。
观察式子,极其简单,只有一个区间求 。
用一个线段树优化一下,就没了。
时间复杂度
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:发现此题没题解,去水一篇了。
练习题
我不会告诉你是因为我还没打
本文作者:AFewSuns
本文链接:https://www.cnblogs.com/AFewSuns/p/15470358.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步