• 动态 DP 学习笔记


    似乎 NOIP2019D2T3 把这个东西宣传的很到位啊。

    我才不会告诉你我已经学过一遍了 早就忘了。

    bzoj4712 洪水 为例。


    首先我们有一个暴力 DP 的式子。

    (dp[x]) 表示以 (x)为根的子树的最小代价,(v[x]) 表示每个点的权值。

    于是转移就是:

    [dp[x] = min(v[x], sum_{yin child_x} dp[y]) ]

    不用动态 DP 的做法

    实际上这道题有一个不用动态 DP 的做法。

    不想看的可以直接跳过。

    [戳我跳过](#动态 DP)

    因为保证了所有的 (to) 都是非负数,那么也就是说,所有的 (v[x]) 都是只增不减的,那么也就意味着,所有的 (dp[x]) 也是只增不减的。

    那么,每个点之多发生一次 (sumlimits_{yin child_x} dp[y])(leq v[x]) 变成 (> v[x])(被修改的那个点除外)。

    于是这种情况也是最多发生 (n+m) 次的。加上 (m) 是因为每一次修改会有可能破坏一次。

    所以如果我们可以得到一种很好的想法:找到所有的 (sumlimits_{yin child_x} dp[y])(v[x]) 的大小关系发生改变的点,然后暴力修改。

    可以发现,从被修改的点的 (dp) 值被增加的量开始,其所有的祖先,最下面会有一段和这个增量相同。直到某一个点的 (sumlimits_{yin child_x} dp[y])(v[x]) 的大小关系发生改变,或者到 (sumlimits_{yin child_x} dp[y] geq v[x]) 停止。所以我们要做的就是通过数据结构维护最近的一次 (sumlimits_{yin child_x} dp[y])(v[x]) 的大小关系发生改变的情况。

    可以直接维护每个点的 (v[x] - sumlimits_{yin child_x} dp[y]) 的结果,然后进行树链剖分, 在重链上可以直接进行线段树二分找出这样的点。

    于是整个题目可以在总 (O((n+m)log^2 n)) 的复杂度完成。

    问题

    某毒瘤:“听说你可以用均摊分析的方法过掉?”

    然后轻轻的敲下了 vim problem.tex,然后找到 n<=200000,保证任意to都为非负数 这一行,将光标停在了那个逗号上,优雅地按下了可怕的 d$,那句“保证任意to都为非负数”瞬间消失得无影无踪。

    动态 DP

    如上文所述,如果不能保证 (to) 为非负数,那么我们之前的均摊分析就不成立,复杂度就直接炸没影了。

    我们考虑使用一种叫作动态 DP 的科技来解决这个问题。

    我们回顾一下上面的 DP 式子:

    [dp[x] = min(v[x], sum_{yin child_x} dp[y]) ]

    如果我们把这棵树进行树链剖分的话,我们先定义一些方便的符号:(child_x) 表示 (x) 的所有孩子的集合;(son_x) 表示 (x) 的重儿子(这个名字怎么充满的封建的味道),(light_x) 表示 (x) 的轻儿子集合。

    那么,如果我们令 (h[x]) 表示 (x) 的所有轻儿子的 (dp) 值之和,即 (h[x] = sumlimits_{yin light_x} dp[y])。则原式可以改写为:

    [dp[x] = min(v[x], h[x]+dp[son_x]) ]

    类似于一般的递推,我们考虑用一种新式的矩阵来表示这种关系:

    对于两个矩阵 (A)(B),定义一种新的乘法运算,令 (C = A imes B),则

    [C_{i, j} = min_{k=1}^n A_{i, k} + B_{k, j} ]

    那么,我们上面的运算可以被用矩阵乘法的形式表示为:

    [egin{bmatrix}h[x] & v[x] \ 0 & 0end{bmatrix} imes egin{bmatrix}dp[son_x] \ 0end{bmatrix} = egin{bmatrix}dp[x] \ 0end{bmatrix} ]

    类似于矩阵快速幂,我们来证明一下我们定义的这一种矩乘具有结合律。

    对于矩阵大小为 (a imes b) 的矩阵 (A),大小为 (b imes c) 的矩阵 (B),大小为 (c imes d) 的矩阵 (C),求证:((A imes B) imes C = A imes (B imes C))

    证明:

    (E = A imes B)(F = B imes C)

    [E_{i, j} = min_{k=1}^b A_{i, k} + B_{k, j}\ F_{i, j} = min_{k=1}^c B_{i, k} + C_{k, j} ]

    然后

    [E imes C = min_{l=1}^c{min_{k=1}^b A_{i, k} + B_{k, l} + C_{l,j}}\ egin{align*} A imes F &= min_{l=1}^b{A_{i,l} + min_{k=1}^c B_{i, k} + C_{k, j}}\ &= min_{k=1}^c{min_{l=1}^b A_{i, l} + B_{i, k} + C_{k, j}}\ &= min_{l=1}^c{min_{k=1}^b A_{i, k} + B_{i, l} + C_{l, j}}\ end{align*} ]

    容易发现 (E imes C = A imes F),即 ((A imes B) imes C = A imes (B imes C))


    (V_x = egin{bmatrix}h[x]&v[x]\0&0 end{bmatrix})

    我们可以发现,如果我们把一条重链上面的点的 (V) 矩阵从某一个点 (x) 开始,按照深度从上到下的顺序乘在一起,最后再乘上一个 (egin{bmatrix}0\0end{bmatrix}),那么我们就可以得到重链顶端的 (x)(dp) 值了。

    上面就是查询的方法了。


    下面我们考虑修改应该怎么做。

    修改会影响到的 (dp) 值是从根到 (x) 的所有点的。——但是我们实际上不需要考虑所有的 (dp) 值,因为 (dp) 值本身没有被存储下来。需要考虑的是影响到了哪些 (h[x])

    每一个点的重儿子的 (dp) 值不影响 (h[x]) 值,被影响的只有每一条重链之间的那一个轻边。所以直接在树剖向上跳的时候用新的 (dp) 值更新一下就好了。


    然后这样的时间复杂度就是一个树链剖分的复杂度 (O(qlog^2n))

    代码如下:

    #include<bits/stdc++.h>
    
    #define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
    #define dbg(...) fprintf(stderr, __VA_ARGS__)
    #define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
    #define fi first
    #define se second
    #define pb push_back
    
    template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
    template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
    
    typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
    
    template<typename I> inline void read(I &x) {
    	int f = 0, c;
    	while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
    	x = c & 15;
    	while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
    	f ? x = -x : 0;
    }
    
    #define lc o << 1
    #define rc o << 1 | 1
    
    const int N = 200000 + 7;
    const ll INF = 0x3f3f3f3f3f3f3f3f;
    
    int n, m, dfc;
    int a[N];
    int dep[N], f[N], siz[N], son[N], dfn[N], pre[N], top[N], bot[N];
    ll dp[N], h[N];
    
    struct Edge { int to, ne; } g[N << 1]; int head[N], tot;
    inline void addedge(int x, int y) { g[++tot].to = y, g[tot].ne = head[x], head[x] = tot; }
    inline void adde(int x, int y) { addedge(x, y), addedge(y, x); }
    
    struct Matrix {
    	ll a[2][2];
    	inline Matrix() { memset(a, 0, sizeof(a)); }
    	inline Matrix operator * (const Matrix &b) {
    		Matrix c;
    		c.a[0][0] = std::min(a[0][0] + b.a[0][0], a[0][1] + b.a[1][0]);
    		c.a[0][1] = std::min(a[0][0] + b.a[0][1], a[0][1] + b.a[1][1]);
    		c.a[1][0] = std::min(a[1][0] + b.a[0][0], a[1][1] + b.a[1][0]);
    		c.a[1][1] = std::min(a[1][0] + b.a[0][1], a[1][1] + b.a[1][1]);
    		return c;
    	}
    } v[N], t[N << 2];
    
    inline void dfs1(int x, int fa = 0) {
    	dep[x] = dep[fa] + 1, f[x] = fa, siz[x] = 1;
    	for fec(i, x, y) if (y != fa) dfs1(y, x), siz[x] += siz[y], siz[y] > siz[son[x]] && (son[x] = y);
    }
    inline void dfs2(int x, int pa) {
    	top[x] = pa, dfn[x] = ++dfc, pre[dfc] = x;
    	if (!son[x]) return (void)(bot[x] = x);
    	dfs2(son[x], pa), bot[x] = bot[son[x]];
    	for fec(i, x, y) if (y != f[x] && y != son[x]) dfs2(y, y);
    }
    inline void dfs3(int x, int fa = 0) {
    	for fec(i, x, y) if (y != fa) {
    		dfs3(y, x);
    		dp[x] += dp[y];
    		if (y != son[x]) h[x] += dp[y];
    	}
    	if (!son[x]) dp[x] = h[x] = INF;
    	smin(dp[x], a[x]);
    }
    
    inline void build(int o, int L, int R) {
    	if (L == R) return t[o].a[0][0] = h[pre[L]], t[o].a[0][1] = a[pre[L]], (void)0;
    	int M = (L + R) >> 1;
    	build(lc, L, M), build(rc, M + 1, R);
    	t[o] = t[lc] * t[rc];
    }
    inline void qadd(int o, int L, int R, int x) {
    	if (L == R) return t[o].a[0][0] = h[pre[L]], t[o].a[0][1] = a[pre[L]], (void)0;
    	int M = (L + R) >> 1;
    	if (x <= M) qadd(lc, L, M, x);
    	else qadd(rc, M + 1, R, x);
    	t[o] = t[lc] * t[rc];
    }
    inline Matrix qsum(int o, int L, int R, int l, int r) {
    	if (l <= L && R <= r) return t[o];
    	int M = (L + R) >> 1;
    	if (r <= M) return qsum(lc, L, M, l, r);
    	if (l > M) return qsum(rc, M + 1, R, l, r);
    	return qsum(lc, L, M, l, r) * qsum(rc, M + 1, R, l, r);
    }
    
    inline ll qry(int x) {
    	const Matrix &tmp = qsum(1, 1, n, dfn[x], dfn[bot[x]]);
    	assert(!tmp.a[1][0] && !tmp.a[1][1]);
    	return std::min(tmp.a[0][0], tmp.a[0][1]);
    }
    inline void upd(int x, int k) {
    	a[x] += k;
    	while (top[x] != 1) {
    		h[f[top[x]]] -= qry(top[x]);
    		qadd(1, 1, n, dfn[x]);
    		h[f[top[x]]] += qry(top[x]);
    		x = f[top[x]];
    	}
    	qadd(1, 1, n, dfn[x]);
    }
    
    inline void work() {
    	dfs1(1), dfs2(1, 1), dfs3(1), build(1, 1, n);
    	read(m);
    	while (m--) {
    		static char opt[5];
    		int x, y;
    		scanf("%s", opt);
    		if (*opt == 'Q') read(x), printf("%lld
    ", qry(x));
    		else read(x), read(y), upd(x, y);
    	}
    	
    }
    
    inline void init() {
    	read(n);
    	for (int i = 1; i <= n; ++i) read(a[i]);
    	int x, y;
    	for (int i = 1; i < n; ++i) read(x), read(y), adde(x, y);
    }
    
    int main() {
    #ifdef hzhkk
    	freopen("hkk.in", "r", stdin);
    #endif
    	init();
    	work();
    	fclose(stdin), fclose(stdout);
    	return 0;
    }
    
  • 相关阅读:
    Windows环境安装tesseract-ocr 4.00并配置环境变量
    python问题集
    使用CefSharp在.Net程序中嵌入Chrome浏览器(八)——Cookie
    python虛擬環境和工具
    pycharm使用(持续更新)
    醒过来的都市
    下一个十年计划6-作品
    下一个十年计划5-方向选择
    下一个十年计划4-反向选择
    负逻辑的实际应用
  • 原文地址:https://www.cnblogs.com/hankeke/p/ddp.html
Copyright © 2020-2023  润新知