动态DP相关

小声bb

去年做了保卫王国之后感觉自己会动态dp了,今天看下全都还给出题人了…
省选前临急抱佛脚吧。

动态dp是什么

就是先给一个dp问题,然后修改里面的一些值 / 给出一些限制,要你快速求出新的dp答案。

方法

大概分三种:

  1. 倍增(适用于无权值修改,只有限制的情况)
  2. 树链剖分
  3. 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));
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值