线段树优化dp学习笔记
到底是什么算法让我觉得两道题就足以让我写一篇学习笔记呢?
虽然两年半以前写过一道dp,正解的优化是单调队列,但是我拿线段树过了(卡着空间过的),所以那个dp并不能叫线段树优化dp。
CF115E Linear Kingdom Races
这个算是最 “原汁原味” 线段树优化dp。
设 表示第 条路到第 条路全修的最大收益。(至于第 到 条路,那不是我们该考虑的,这就是dp的精髓)那么有转移:
其中: 和 分别表示右端点在 且被区间 覆盖的的比赛收益之和 和 左端点和右端点都在 的比赛收益之和。 那为何在 时只用考虑 的比赛呢?因为 的比赛都在 考虑过了,而 的不是现在该考虑的。
但是,这题不可能这么简单,单是存状态和枚举状态就可以让我们爆炸了 先来优化空间:我们发现: 就是对于 的答案,于是第二个方程就可以表示成 。而我们发现第一个方程又只会从 来转移,那么我们可以考虑什么?滚动数组啊。那么空间就优化完了。
现在来优化时间。我们设每一个 都有一个待定值数组 ,那么 一定由 里的某一个值转移而来。对于暴力来说,就是每次新枚举一个新的状态,都给他一个新的 数组,但是我们要优化,我们就不可能每次用 给他一个新的 数组,我们应该继承上一个状态的 数组,并操作它使它变成当前状态的 数组。我们发现,每次转移, 数组的 项都会加上 ,也会加上 。这不就是区间操作吗?我们就可以用线段树来维护这个值。线段树里装的就是待定值数组 。而对于 的情况,因为本来只有 的下标里有值,而我们要用到 的值,我们就可以给他赋值一个 ,正如同方程里的一样,而题目里让我们求最大的收益,那么线段树查询的就是区间 里的最大值。这样,我们就做完了。
有些细节比如如何记录 的比赛会在代码里说明。
CPP
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
#include<bits/extc++.h>
#define int long long
#define ls (rt << 1)
#define rs (rt << 1 | 1)
using namespace std;
const int maxn = 2e5 + 5;
int n,m;
int a[maxn];
struct edge
{
int l,p;
edge *nxt;
}*head[maxn];
struct Nahida//不要关心这个名字
{
int l,r;
int val,lazy;
}tree[maxn << 2];
void push_up(int rt){tree[rt].val = max(tree[ls].val,tree[rs].val);}
void push_down(int rt)
{
if (!tree[rt].lazy)
return;
tree[ls].val += tree[rt].lazy;
tree[rs].val += tree[rt].lazy;
tree[ls].lazy += tree[rt].lazy;
tree[rs].lazy += tree[rt].lazy;
tree[rt].lazy = 0;
}
void build(int l,int r,int rt)
{
tree[rt].l = l;
tree[rt].r = r;
if (l == r)
{
tree[rt].val = tree[rt].lazy = 0;
return;
}
int mid = (l + r) >> 1;
build(l,mid,ls);
build(mid + 1,r,rs);
}
void upd(int ql,int qr,int x,int rt = 1)
{
int l = tree[rt].l;
int r = tree[rt].r;
if (ql <= l && r <= qr)
{
tree[rt].val += x;
tree[rt].lazy += x;
return;
}
push_down(rt);
int mid = (l + r) >> 1;
if (ql <= mid)
upd(ql,qr,x,ls);
if (qr > mid)
upd(ql,qr,x,rs);
push_up(rt);
}
void adde(int l,int r,int p)
{
auto tmp = new edge;
tmp->l = l;
tmp->p = p;
tmp->nxt = head[r];
head[r] = tmp;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
int l,r,p;
for (int i = 1; i <= m; i++)
{
cin >> l >> r >> p;
adde(l,r,p);//用链式前向星(邻接表)记录右端点为i的比赛。
}
build(1,n,1);
int ans = 0;
for (int i = 1; i <= n; i++)//滚动数组滚掉一维,就可以存下了。
{//这里没有重新建树就是继承了上一个的tmp数组
upd(i,i,ans);//单点修改,增添一个ans
upd(1,i,-a[i]);//上面说的:区间更改
for (auto j = head[i]; j; j = j->nxt)
upd(1,j->l,j->p);//也是区间修改
ans = max(ans,tree[1].val);//记录答案
}
cout << ans;
return 0;
}