线段树优化DP学习笔记 & JZOJ 孤独一生题解

DP 的世界里
有一种题需要单调队列优化 DP
一般在此时,fi 和它的决策集合 fj 在转移时 i 不和 j 粘在一起(即所有的 j 转移到 i时 关于j 的部分全都与 i无关),
果真如此,我们就可以用单调队列优化,留下可能用到的更有决策
而很多情况下 i,j 有联系,i 对于不同的 j ,转移计入的贡献和 i,j 都有联系,如
fi=minj=1j<ifj+si1sj+|hihj1|
此时单调队列就无用武之地了
那怎么办
各种奇妙的优化

本文我们来学习线段树优化 DP,解决上面问题

在例题中感受美

JZOJ孤独一生

题目大意:
将序列 H 划分为两个可空集合。
对于一个集合 S=P1,P2,...P|S|,其中要求 P1<P2<...<P|S|,它的花费是 i=1|S||HPiHPi1|
最小化花费
解法:
还好的 dp
fi 表示处理完 1i 个元素的最小花费
那么转移考虑当前的 i 所属集合前一个元素是谁
枚举一个 jj1i 同属一个集合
那么转移就是 fi=minj=1j<ifj+si1sj+|hihj1|
其中 si=si1+|hihi1|

一眼望去 O(n2)
再望,绝对值太糟糕(单调队列挂了花)
怎办?
去!
用线段树维护
······
不会啊?

如何用线段树
君不见,绝对值从天上来,纠缠 (i,j) 不可休······
插!
分类讨论,绝对值分开,把式子变好看,这样 i,j 就分开了
1hi>hjfi=fj+si1sj+hihj1
整理得 fi=(si1+hi)+(fjsjhj1)
2hi>hjfi=fj+si1sjhi+hj1
整理得 fi=(si1hi)+(fjsj+hj1)

总算把 (i,j) 分开了
此时做商量

如何让 1 式最小,因为决定于 j,所以让后面一堆最小
发现可以用线段树维护 fjsjhj1 的最小值
因为判定时和 j1 有关,所以维护以 hj1 为下标的权值线段树
具体来说就是在 hj1 的位置插入值 fjsjhj1
因为顺序枚举,所以算完一个插一个,保证正确性
只需取出线段树中 0hi 的最小值即可

2 式同理(再开一棵权值线段树,因为另一种 j 的贡献长得不一样)

代码(常数巨大,不得不开O)

#pragma GCC optimize(2)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 500000;
int n , h[N + 5] , id[N + 5];
LL f[N + 5] , s[N + 5];
inline LL Min(LL x , LL y) { return x < y ? x : y; }
inline int Abs(int x) { return x < 0 ? -x : x; }
struct node{ int l , r; }e[N + 5];
struct tree{
LL tr[(N << 2) + 5];
inline void full() { memset(tr , 120 , sizeof(tr)); }
inline void change(int k , int l , int r , int x , LL v)
{
tr[k] = Min(tr[k] , v);
if (l == r) return;
register int mid = (l + r) >> 1;
if (x <= mid) change(k << 1 , l , mid , x , v);
else change(k << 1 | 1 , mid + 1 , r , x , v);
}
inline LL query(int k , int l , int r , int x , int y)
{
if (l >= x && r <= y) return tr[k];
register int mid = (l + r) >> 1;
register LL res = 1e18;
if (x <= mid) res = Min(res , query(k << 1 , l , mid , x , y));
if (y > mid) res = Min(res , query(k << 1 | 1 , mid + 1 , r , x , y));
return res;
}
}p,q;
inline int read()
{
register char ch = getchar();
register LL res = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') res = res * 10 + ch - '0' , ch = getchar();
return (int)res;
}
inline bool cmp(node x , node y) { return x.l < y.l; }
int main()
{
// freopen("a.in" , "r" , stdin);
n = read();
for(register int i = 1; i <= n; i++)
{
h[i] = read() , e[i].l = h[i] , e[i].r = i;
s[i] = s[i - 1] + (LL)Abs(h[i] - h[i - 1]);
}
sort(e + 1 , e + n + 1 , cmp);
for(register int i = 1; i <= n; i++) id[e[i].r] = i;
q.full() , p.full();
q.change(1 , 0 , n , 0 , 0);
p.change(1 , 0 , n , 0 , 0);
f[0] = 0;
for(register int i = 1; i <= n; i++)
{
LL x = q.query(1 , 0 , n , 0 , id[i]);
LL y = p.query(1 , 0 , n , id[i] , n);
f[i] = Min(s[i - 1] + h[i] + x , s[i - 1] - h[i] + y);
q.change(1 , 0 , n , id[i - 1] , f[i] - s[i] - h[i - 1]);
p.change(1 , 0 , n , id[i - 1] , f[i] - s[i] + h[i - 1]);
}
for(register int i = 1; i <= n; i++) f[n] = Min(f[n] , f[i] + s[n] - s[i]);
printf("%lld" , f[n]);
}

很多时候,线段树优化 DP 的具体方法因题而异,不可一概而论
要想更好的掌握,就要多做题

posted @   leiyuanze  阅读(286)  评论(1编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示