小声bb
去年做了保卫王国之后感觉自己会动态dp了,今天看下全都还给出题人了…
省选前临急抱佛脚吧。
动态dp是什么
就是先给一个dp问题,然后修改里面的一些值 / 给出一些限制,要你快速求出新的dp答案。
方法
大概分三种:
- 倍增(适用于无权值修改,只有限制的情况)
- 树链剖分
- LCT
其中树剖是比较不推荐的,太长了。但是树剖比较好理解,可以用这个来学。
注意不要拘泥于原有的完整树形结构。为了快速维护一些值,必须要把他拆开多部分来看。
拿一道例题来讲好了。
https://www.luogu.org/problemnew/show/P4719
我们先轻重链剖分,然后观察他的dp式子。发现这个dp非常简单,并且能快速去掉 / 添加一颗子树。
这就给我们动态dp可乘之机。对于轻链,我们把它对父亲的贡献考虑成一个定值,即:父亲取s时,这个点和他下辖的所有轻链子树的总贡献。
然后在链上,我们可以用线段树维护
f
[
l
]
[
r
]
f[l][r]
f[l][r]表示每一个区间最左边取值,最右边取值,这样的这一段最大值。
合并很简单。形式有点类似矩阵乘法,看个人理解了。
这样每修改一个地方,就把它一路更新上来,嘴巴起来很简单,细节比较多…
LCT
使用:LCT维护这个东西无疑更简单。注意到我们不需要实现mkroot,所以连翻转都不需要。
维护每一颗splay中,最左边选L,最右边选R的最优dp值。
合并差不多,就是要考虑上中间点的贡献。
然后求子树的话,就要考虑上虚边贡献,这个就类似树剖里面的轻边所带来的影响。
假如不会维护子树信息的话,可以看我之前的blog:
https://blog.csdn.net/jokerwyt/article/details/83217458
倍增
最后说说倍增好了,就是保卫王国那样。设f[i][j][s1][s2]表示i的状态是s1,i的2^j级祖先状态是s2,这一段的最优贡献是什么。
优点是相比其他两种常数小,代码短。缺点是无法支持修改权值。
最后贴模板:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int final[N], to[N * 2], nex[N * 2], tot, va[N];
int c[N][2], fa[N];
int f[N][2][2];
//当前这颗splay左边是xx右边是xx的权值
int fv[N][2];
//x的虚树上+x自己能贡献的权值
void link(int x,int y) {
to[++tot] = y, nex[tot] = final[x], final[x] = tot;
}
int maxall(int x) {
return max(f[x][0][0], max(f[x][0][1], max(f[x][1][0], f[x][1][1])));
}
void upd(int x) {
static int w[2][2], nw[2][2];
memset(f[x],0,sizeof f[x]);
if(c[x][0]){
memcpy(w, f[c[x][0]], sizeof f[x]);
memset(nw,0,sizeof nw);
for(int l = 0; l < 2; l++) {
for(int r = 0; r < 2; r++) {
for(int mid = 0; mid < 2; mid ++) if (mid + r != 2) {
nw[l][mid] = max(nw[l][mid], w[l][r] + fv[x][mid]);
}
}
}
memcpy(f[x], nw, sizeof nw);
} else {
for(int s = 0; s < 2; s++) f[x][s][s] = fv[x][s];
}
if (c[x][1]) {
memcpy(w, f[x], sizeof w);
memset(nw, 0, sizeof nw);
for(int l = 0; l < 2; l++) {
for(int r = 0; r < 2; r++) {
for(int nl = 0; nl < 2; nl ++) if (r + nl != 2) {
for(int nr = 0; nr < 2; nr++) {
nw[l][nr] = max(nw[l][nr], w[l][r] + f[c[x][1]][nl][nr]);
}
}
}
}
memcpy(f[x], nw, sizeof nw);
}
}
void dfs(int x,int fw) {
fv[x][1] = va[x];
fa[x] = fw;
for(int i = final[x]; i; i=nex[i]) {
int y = to[i];
if(y != fw) dfs(y, x);
}
upd(x);
if (fa[x]) {
fv[fa[x]][0] += maxall(x);
fv[fa[x]][1] += max(f[x][0][0], f[x][0][1]);
}
}
#define got(x) ((c[fa[x]][1] == (x)))
#define isroot(x) (c[fa[x]][1] != (x) && c[fa[x]][0] != (x))
void rotate(int x) {
int y = fa[x], z = got(x);
c[y][z] = c[x][1 - z];
if (c[x][1 - z]) fa[c[x][1 - z]] = y;
fa[x] = fa[y];
if(!isroot(y)) c[fa[y]][got(y)] = x;
fa[y] = x;
c[x][1 - z] = y;
upd(y);
//upd(x);
}
void splay(int x) {
while (!isroot(x)) {
if(!isroot(fa[x])) {
if (got(x) == got(fa[x])) {
rotate(fa[x]);
} else rotate(x);
}
rotate(x);
}
upd(x);
}
void access(int x){
for(int i = 0; x; i = x, x = fa[x]) {
splay(x);
int y = c[x][1];
fv[x][0] += maxall(y);
fv[x][1] += max(f[y][0][0], f[y][0][1]);
fv[x][0] -= maxall(i);
fv[x][1] -= max(f[i][0][0], f[i][0][1]);
c[x][1] = i;
upd(x);
}
}
int main(){
freopen("a.in","r",stdin);
cin>>n>>m;
for(int i = 1; i <= n; i++) scanf("%d", &va[i]);
for(int i = 1; i < n; i++) {
int u, v; scanf("%d %d",&u,&v);
link(u, v),link(v, u);
}
dfs(1, 0);
for(int i = 1; i <= m; i++) {
int x,y; scanf("%d %d", &x,&y);
access(x);
splay(x);
fv[x][1] -= va[x];
va[x] = y;
fv[x][1] += va[x];
upd(x);
printf("%lld\n",maxall(x));
}
}