浅谈动态dp

(大佬勿喷)

(请不要嘲笑我讲得不清楚,因为我太菜了。。。)

(请不要嘲笑我看了题解再写题解,因为我实在是太菜了。。。)

P4719 [模板]“动态 DP”&动态树分治

d p [ x ] [ 0 ] dp[x][0] dp[x][0] 为以 x x x 为根不选 x x x 的最大权独立集的大小。

d p [ x ] [ 1 ] dp[x][1] dp[x][1] 为以 x x x 为根选 x x x 的最大权独立集的大小。

y y y x x x 的子节点。

这题可以很容易写出dp式:

d p [ x ] [ 0 ] = ∑ m a x ( d p [ y ] [ 0 ] , d p [ y ] [ 1 ] ) dp[x][0]=\sum {max(dp[y][0],dp[y][1])} dp[x][0]=max(dp[y][0],dp[y][1])

d p [ x ] [ 1 ] = a [ x ] + ∑ d p [ y ] [ 0 ] dp[x][1]=a[x]+\sum dp[y][0] dp[x][1]=a[x]+dp[y][0]

a [ x ] a[x] a[x] 即为 x x x 的权值,最后的答案是 m a x ( d p [ x ] [ 0 ] , d p [ x ] [ 1 ] ) max(dp[x][0],dp[x][1]) max(dp[x][0],dp[x][1])

若暴力修改,时间复杂度 O ( n m ) O(nm) O(nm)

当然,很容易会发现,我们只用修改从要修改的点到根节点(这里设为 1 1 1)路径上的点即可。

稍微改一下,时间复杂度 O ( m d ) O(md) O(md)(其中 d d d 为深度)


进入正题。不妨看看刚才我说了啥。

我们只用修改从要修改的点到根节点(这里设为 1 1 1)路径上的点即可。

ok,相信聪明的你已经想到了树链剖分。(听说可以用什么全局平衡二叉树做的,但我太菜了不会。。。)

那重链中怎么更新呢?我们定义 s o n son son 为点 x x x 的重儿子。

再看看这个式子。

d p [ x ] [ 0 ] = ∑ m a x ( d p [ y ] [ 0 ] , d p [ y ] [ 1 ] ) dp[x][0]=\sum {max(dp[y][0],dp[y][1])} dp[x][0]=max(dp[y][0],dp[y][1])

d p [ x ] [ 1 ] = a [ x ] + ∑ d p [ y ] [ 0 ] dp[x][1]=a[x]+\sum dp[y][0] dp[x][1]=a[x]+dp[y][0]

看着 ∑ \sum 不爽,而且便于用到 s o n son son 变量,不妨把轻儿子都拣出来,再添加一个概念。

定义 f [ x ] [ 1 ] f[x][1] f[x][1] x x x 的所有轻儿子都不选,再选 x x x 最大权独立集的大小,定义 f [ x ] [ 0 ] f[x][0] f[x][0] 为所有的轻儿子可选可不选,不选 x x x 的最大权独立集的大小。

d p [ x ] [ 0 ] = f [ x ] [ 0 ] + max ⁡ ( d p [ s o n ] [ 0 ] , d p [ s o n ] [ 1 ] ) dp[x][0]=f[x][0]+\max(dp[son][0],dp[son][1]) dp[x][0]=f[x][0]+max(dp[son][0],dp[son][1])

d p [ x ] [ 1 ] = f [ x ] [ 1 ] + d p [ s o n ] [ 0 ] dp[x][1]=f[x][1]+dp[son][0] dp[x][1]=f[x][1]+dp[son][0]

我们不妨把转移写成矩阵的形式。你会问了,这明明就不像矩乘呀,别慌,定义一个新的运算符 × \times ×,对于矩阵 A 、 B A、B AB,定义 C C C A × B A\times B A×B,则 C [ i ] [ j ] = max ⁡ k { A [ i ] [ k ] + B [ k ] [ j ] } C[i][j]=\max_k\{A[i][k]+B[k][j]\} C[i][j]=maxk{A[i][k]+B[k][j]}(注意这个也是满足矩阵结合律的)

再换一下dp式

d p [ x ] [ 0 ] = max ⁡ ( f [ x ] [ 0 ] + d p [ s o n ] [ 0 ] , f [ x ] [ 0 ] + d p [ s o n ] [ 1 ] ) dp[x][0]=\max(f[x][0]+dp[son][0],f[x][0]+dp[son][1]) dp[x][0]=max(f[x][0]+dp[son][0],f[x][0]+dp[son][1])

d p [ x ] [ 1 ] = m a x ( f [ x ] [ 1 ] + d p [ s o n ] [ 0 ] , − ∞ ) dp[x][1]=max(f[x][1]+dp[son][0],-\infty) dp[x][1]=max(f[x][1]+dp[son][0],)

推一推,发现
[ d p [ s o n ] [ 0 ] d p [ s o n ] [ 1 ] ] ∗ [ f [ x ] [ 0 ] f [ x ] [ 1 ] f [ x ] [ 0 ] − ∞ ] = [ d p [ x ] [ 0 ] d p [ x ] [ 1 ] ] \left[ \begin{matrix} dp[son][0] & dp[son][1] \end{matrix}\right] * \left[\begin{matrix} f[x][0]&f[x][1]\\f[x][0]& -\infty\end{matrix}\right]=\left[ \begin{matrix} dp[x][0]&dp[x][1] \end{matrix}\right] [dp[son][0]dp[son][1]][f[x][0]f[x][0]f[x][1]]=[dp[x][0]dp[x][1]]
注意这里的 d p dp dp 矩阵是横着的。(根据我算矩阵的习惯来说必须这样)

对于此时,查询答案的话在线段树上跑,将所有元素乘起来再乘 [ 0 , − ∞ ] [0,-\infty] [0,] 即可,时间复杂度: O ( l o g 2 ( n ) ) O(log^2(n)) O(log2(n))

修改的话就像树剖那样,改每条链即可(这里需要用上一条链的链顶改下一条链的链顶),时间复杂度: O ( l o g 2 ( n ) ) O(log^2(n)) O(log2(n))

就是这样了,具体见代码吧。。。

总时间复杂度: O ( n l o g 2 ( n ) ) O(nlog^2(n)) O(nlog2(n))

Code

再让我调一下。。。
只能明早做二分图了。。。

upd:放个代码,事实上,这篇文章是我初二写的,但是代码是三年后调对的。

现在再来看此文,觉得还是写的很清晰的。

/*
世界の果てさえ
【世界的尽头在何处】
仆らは知らない
【我们也无从知晓】
*/
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#include <set>
#include <map>
#define pr pair <int, int>
#define mr make_pair
#define LL long long
#define ls tree[p].L
#define rs tree[p].R
using namespace std;
const int MAXN = 1e5 + 5, inf = 0x3f3f3f3f;
struct node {
	LL s[2][2];
	node() {
		for(int i = 0; i <= 1; i ++) for(int j = 0; j <= 1; j ++) s[i][j] = -inf;
	}
	node operator * (const node P) const {
		node ans;
		for(int k = 0; k <= 1; k ++) {
			for(int i = 0; i <= 1; i ++) {
				for(int j = 0; j <= 1; j ++) ans.s[i][j] = max(ans.s[i][j], s[i][k] + P.s[k][j]);
			}
		}
		return ans;
	}
}mat1[MAXN];
struct sgt {
	int L, R;
	node mat; 
}tree[MAXN << 2];
int n, m, a[MAXN], dfn[MAXN], mp[MAXN], son[MAXN];
int tot, d[MAXN], siz[MAXN], Fa[MAXN], tp[MAXN], ed[MAXN];
LL dp[MAXN][2];
vector <int> v[MAXN];
void read(int &x) {
	x = 0; bool f = 1; char C = getchar();
	for(; C < '0' || C > '9'; C = getchar()) if(C == '-') f = 0;
	for(; C >= '0' && C <= '9'; C = getchar()) x = (x << 1) + (x << 3) + (C ^ 48);
	x = (f ? x : -x);
}
void dfs(int x, int fa) {
	siz[x] = 1; Fa[x] = fa; d[x] = d[fa] + 1; dp[x][1] = a[x]; dp[x][0] = 0;
	for(auto y : v[x]) {
		if(y == fa) continue;
		dfs(y, x);
		siz[x] += siz[y]; dp[x][0] += max(dp[y][0], dp[y][1]); dp[x][1] += dp[y][0];
		if(siz[son[x]] < siz[y]) son[x] = y;
	}
}
void dfs1(int x, int t) {
	tp[x] = t; dfn[++ tot] = x; mp[x] = tot; mat1[x].s[0][1] = a[x]; mat1[x].s[1][1] = -inf;
	mat1[x].s[0][0] = mat1[x].s[1][0] = 0;
	ed[t] = tot;
	if(son[x]) dfs1(son[x], t);
	for(auto y : v[x]) {
		if(y == son[x] || y == Fa[x]) continue;
		dfs1(y, y); mat1[x].s[0][0] += max(dp[y][0], dp[y][1]); mat1[x].s[1][0] += max(dp[y][0], dp[y][1]); 
		mat1[x].s[0][1] += dp[y][0]; 
	}
}
void build(int p, int l, int r) {
	tree[p].L = l; tree[p].R = r;
	if(l == r) {
		tree[p].mat = mat1[dfn[l]];
//		for(int i = 0; i <= 1; i ++) {
//			for(int j = 0; j <= 1; j ++) {
//				printf("%lld ", tree[p].mat.s[i][j]);
//			}
//			printf("\n");
//		}
//		printf("|%d|\n", l);
		return;
	}
	int mid = (l + r) >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
	tree[p].mat = tree[p << 1 | 1].mat * tree[p << 1].mat;
}
void cng(int p, int x) {
	if(tree[p].L == tree[p].R) {
		tree[p].mat = mat1[dfn[x]]; return;
	}
	int mid = (tree[p].L + tree[p].R) >> 1;
	if(x <= mid) cng(p << 1, x);
	else cng(p << 1 | 1, x);
	tree[p].mat = tree[p << 1 | 1].mat * tree[p << 1].mat;
}
node ask(int p, int ql, int qr) {
	if(tree[p].L >= ql && tree[p].R <= qr) return tree[p].mat;
	int mid = (tree[p].L + tree[p].R) >> 1;
	if(mid < ql) return ask(p << 1 | 1, ql, qr);
	if(mid >= qr) return ask(p << 1, ql, qr);
	node q = ask(p << 1, ql, qr), r = ask(p << 1 | 1, ql, qr);
	return r * q;
}
void modify(int x, int y) {
	mat1[x].s[0][1] += y - a[x]; a[x] = y;
	while(x) {
		node p = ask(1, mp[tp[x]], ed[tp[x]]); cng(1, mp[x]);
		node q = ask(1, mp[tp[x]], ed[tp[x]]); x = Fa[tp[x]];
		mat1[x].s[0][0] -= max(p.s[0][0], p.s[0][1]); mat1[x].s[0][0] += max(q.s[0][0], q.s[0][1]);
		mat1[x].s[1][0] -= max(p.s[0][0], p.s[0][1]); mat1[x].s[1][0] += max(q.s[0][0], q.s[0][1]);
		mat1[x].s[0][1] -= p.s[0][0]; mat1[x].s[0][1] += q.s[0][0];
	}
}
int main() {
	read(n); read(m); int x, y;
	for(int i = 1; i <= n; i ++) read(a[i]);
	for(int i = 1; i <= n - 1; i ++) {
		read(x); read(y); v[x].emplace_back(y); v[y].emplace_back(x);
	}
	dfs(1, 0); dfs1(1, 1); build(1, 1, n);
	for(int i = 1; i <= m; i ++) {
		read(x); read(y); modify(x, y);
		node t = ask(1, 1, ed[1]); printf("%lld\n", max(t.s[0][0], t.s[0][1]));
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值