[學習筆記]動態動態規劃/動態DP/DDP

概述

\(DDP\)是指一類須要支持修改的\(DP\)問題,常見的主要是帶修樹形\(DP\),能夠用樹鏈剖分結合矩陣乘法優化修改的複雜度ios

詳解

從例題來分析:洛谷P4719函數

題目大意:給出\(n\)個點的樹,每一個點有點權,共\(m\)次操做,每次修改一個點的點權,求每次修改後樹的最大權獨立集的權值大小優化

\(n, m \le 1e5\)spa


若是不帶修改,容易想到設\(f_{u, 0/1}\)來表示以\(u\)爲根的子樹,選/不選\(u\)的答案,推出轉移:
\[ \begin{align} f_{u, 0} & = \sum_{v \in son_u} \max(f_{v, 0}, f_{v, 1}) \\ f_{u, 1} & = w_u + \sum_{v \in son_u} f_{v, 0} \end{align} \]
答案就是\(\max (f_{1, 0}, f_{1, 1})\)設計

可是這樣單次修改須要修改到根的路徑上的全部點,是\(O(n)\)code

既然修改的是一條路徑,不妨試試樹鏈剖分get

可是按上面的方式轉移顯然不能將一條鏈上的轉移合併,因此考慮從新設計一下狀態,\(f\)的含義不變,增長一個\(g_{u, 0/1}\)表示只考慮\(u\)的輕子樹的答案,那麼就有:
\[ \begin{align} g_{u, 0} & = \sum_{v \in lson_u} \max(f_{v, 0}, f_{v, 1}) \\ g_{u, 1} & = w_u + \sum_{v \in lson_u} f_{v, 0} \\ f_{u, 0} & = g_{u, 0} + \max(f_{hv[u], 0}, f_{hv[u], 1}) \\ f_{u, 1} & = g_{u, 1} + f_{hv[u], 0} \end{align} \]string

觀察後兩式中\(f\)\(g\)的關係咱們發現貌似能夠寫成形如矩陣乘法的形式,若是咱們從新定義矩陣乘法爲\(C_{i, j} = \max_{k = 1}^{n} (A_{i, k} + B_{k, j})\),就會有:
\[ \left[ \begin{matrix} f_{hv[u], 0} & f_{hv[u], 1} \end{matrix} \right] * \left[ \begin{matrix} g_{u, 0} & g_{u, 1} \\ g_{u, 0} & -\infty \end{matrix} \right] = \left[ \begin{matrix} f_{u, 0} & f_{u, 1} \end{matrix} \right] \]
容易證實新定義的矩陣乘法具備結合律,那麼就能夠樹鏈剖分後用線段樹維護重鏈上第二個矩陣的積it

因而咱們能夠發現:io

  1. 若是一個點是鏈頂,它的\(f\)會對它父親的\(g\)產生貢獻
  2. 不然,它的\(g\)會對它所在鏈的鏈頂產生貢獻,且已經統計進線段樹中

因此咱們只須要線段樹維護\(g\)的積,同時維護下每條鏈鏈頂的\(f\)就好了

具體作法就是先在線段樹中更新當前節點的\(g\),而後更新當前鏈頂的\(f\),而後跳到鏈頂的父親,重複這個過程就能夠了,更具體的可見代碼的\(modify\)函數

複雜度\(O(n \log^2 n)\)

一個細節:矩陣乘法不具備交換律,注意是從下往上乘,按\(dfs\)序從大到小乘

PS.此題還有\(O(n \log n)\)\(LCT\)作法

代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define MAXN 100005

typedef long long LL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
struct Matrix {
    LL data[2][2];
    Matrix() { memset(data, 0, sizeof data); }
    Matrix(LL a00, LL a01, LL a10, LL a11) { data[0][0] = a00, data[0][1] = a01, data[1][0] = a10, data[1][1] = a11; }
    static Matrix indentity() { return Matrix(1, 0, 0, 1); }
    Matrix operator *(const Matrix &) const;
};
struct SegmentTree {
    Matrix data[MAXN << 2];
    void modify(int, int, int, int, const Matrix &);
    void query(int, int, int, int, int, Matrix &);
};

char gc();
int read();
void dfs1(int);
void dfs2(int);
void modify(int, int);

int N, M, val[MAXN];
int idx, top[MAXN], bot[MAXN], dep[MAXN], fa[MAXN], dfn[MAXN], size[MAXN], heavy[MAXN];
LL f[MAXN][2], g[MAXN][2];
std::vector<int> trans[MAXN];
SegmentTree sgt;

int main() {
    //freopen("tmp.in", "r", stdin);
    //freopen("tmp.out", "w", stdout);
    
    N = read(), M = read();
    for (int i = 1; i <= N; ++i) val[i] = read();
    for (int i = 1; i < N; ++i) {
        int u = read(), v = read();
        trans[u].push_back(v);
        trans[v].push_back(u);
    }
    dfs1(1);
    top[1] = 1;
    dfs2(1);
    while (M--) {
        int x = read(), y = read();
        modify(x, y);
        printf("%lld\n", std::max(f[1][0], f[1][1]));
    }
    return 0;
}
inline char gc() {
    static char buf[1000000], *p1, *p2;
    if (p1 == p2) p1 = (p2 = buf) + fread(buf, 1, 1000000, stdin);
    return p1 == p2 ? EOF : *p2++;
}
inline int read() {
    int res = 0, op; char ch = gc();
    while (ch != '-' && (ch < '0' || ch > '9')) ch = gc();
    op = (ch == '-' ? ch = gc(), -1 : 1);
    while (ch >= '0' && ch <= '9') res = (res << 1) + (res << 3) + ch - '0', ch = gc();
    return res * op;
}
void dfs1(int u) {
    dep[u] = dep[fa[u]] + 1;
    size[u] = 1;
    for (int i = 0; i < trans[u].size(); ++i) {
        int v = trans[u][i];
        if (v ^ fa[u]) {
            fa[v] = u, dfs1(v);
            size[u] += size[v];
            if (!heavy[u] || size[v] > size[heavy[u]]) heavy[u] = v;
        }
    }
}
void dfs2(int u) {
    dfn[u] = ++idx;
    g[u][0] = 0, g[u][1] = val[u];
    if (heavy[u]) {
        top[heavy[u]] = top[u];
        dfs2(heavy[u]);
        bot[u] = bot[heavy[u]];
    } else bot[u] = u;
    for (int i = 0; i < trans[u].size(); ++i) {
        int v = trans[u][i];
        if (v == fa[u] || v == heavy[u]) continue;
        top[v] = v, dfs2(v);
        g[u][0] += std::max(f[v][0], f[v][1]);
        g[u][1] += f[v][0];
    }
    f[u][0] = g[u][0] + std::max(f[heavy[u]][0], f[heavy[u]][1]);
    f[u][1] = g[u][1] + f[heavy[u]][0];
    sgt.modify(1, 1, N, dfn[u], Matrix(g[u][0], g[u][1], g[u][0], -INF));
}
Matrix Matrix::operator *(const Matrix &m) const {
    Matrix res;
    res.data[0][0] = std::max(data[0][0] + m.data[0][0], data[0][1] + m.data[1][0]);
    res.data[0][1] = std::max(data[0][0] + m.data[0][1], data[0][1] + m.data[1][1]);
    res.data[1][0] = std::max(data[1][0] + m.data[0][0], data[1][1] + m.data[1][0]);
    res.data[1][1] = std::max(data[1][0] + m.data[0][1], data[1][1] + m.data[1][1]);
    return res;
}
void SegmentTree::modify(int rt, int L, int R, int pos, const Matrix &m) {
    if (L == R) data[rt] = m;
    else {
        int mid = (L + R) >> 1;
        if (pos <= mid) modify(rt << 1, L, mid, pos, m);
        else modify(rt << 1 | 1, mid + 1, R, pos, m);
        data[rt] = data[rt << 1 | 1] * data[rt << 1];//注意乘的順序 
    }
}
void SegmentTree::query(int rt, int L, int R, int l, int r, Matrix &res) {
    if (L >= l && R <= r) res = res * data[rt];//注意乘的順序 
    else {
        int mid = (L + R) >> 1;
        if (r > mid) query(rt << 1 | 1, mid + 1, R, l, r, res);
        if (l <= mid) query(rt << 1, L, mid, l, r, res);
    }
}
void modify(int x, int y) {
    g[x][1] = g[x][1] - val[x] + y;
    val[x] = y;
    while (x) {
        int t = top[x], b = bot[x];
        sgt.modify(1, 1, N, dfn[x], Matrix(g[x][0], g[x][1], g[x][0], -INF));//在線段樹中修改g 
        Matrix tmp;
        sgt.query(1, 1, N, dfn[t], dfn[b], tmp);//查出鏈頂f的新值 
        g[fa[t]][0] -= std::max(f[t][0], f[t][1]);//更新鏈頂父親的g 
        g[fa[t]][1] -= f[t][0];
        f[t][0] = tmp.data[0][0], f[t][1] = tmp.data[0][1];//更新鏈頂 
        g[fa[t]][0] += std::max(f[t][0], f[t][1]);
        g[fa[t]][1] += f[t][0];
        x = fa[t];//跳到鏈頂的父親 
    }
}
//Rhein_E
相關文章
相關標籤/搜索