Luogu P4643 【模板】动态dp(矩阵乘法,线段树,树链剖分)

题面

给定一棵 n 个点的树,点带点权。

m 次操作,每次操作给定 x,y ,表示修改点 x 的权值为 y

你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

题解

如题所示 , 是个模板题 ...

首先考虑静态 dp , 令 dpu,0/1u 不存在 / 存在 于最大权独立集的权值大小 .

然后转移很显然 , 一个点存在于独立集中时 , 儿子全都不能选 . 不存在时 , 儿子可选可不选 .

vu 的儿子 , 那么转移就有 :

{dpu,0=vmax{dpv,1dpv,0}dpu,1=valu+vdpu,0

这个如果每次修改直接做是 O(n2) 的 , 不能满足要求 .

然而此题是随机数据 . 每次修改单点权值 , 只需要修改这个点到根的 dp 值就行了 , 期望复杂度 O(qlogn+n) .

然后跑的飞快 , 怒拿 Luogu rank2.... 但这种随便一条链 , 或者扫帚就挂了 ..

下面就要引入正解了 .

也就是对于这种只有加法和取 max 操作的 dp 我们可以考虑构造矩阵 .

也就是普通矩阵乘法进行改变 , 把 × 变成 + , + 变成 max .

也就是 Ci,j=kAi,k×Bk,j 变成 Ci,j=maxk{Ai,k+Bk,j} 就行了.

不难发现这个仍然满足结合律 . (可以用展开来证明 , 或者类似于 Floyd 的方法理解)

首先树链剖分 , 考虑链上如何修改和询问 . 不难构造矩阵 .

[00valu]×[dpi,0dpi,1]=[dpi,0dpi,1]

然后我们考虑用线段树维护前面那个矩阵 , 然后每次 O(logn) 修改和询问就行了 .

但扩展到树上的时候有些麻烦 , 因为对于一个点可能存在多个儿子 .

但此时它最多只会有一个重儿子 , 我们考虑记下他所有的轻儿子对这个点贡献后的矩阵就行了.

也就是说 令 gu,0/1 为之前 dpu,0/1 去掉重儿子得到的答案 , 那么此时的转移矩阵就变成了 .

[gu,0gu,0gu,10]×[dpson[u],0dpson[u],1]=[dpu,0dpu,1]

请自己展开验证...

然后每次修改的时候 , 只需要在它重链底端的矩阵上进行修改就行了 . (第一个点直接改本身)

修改的话 , 就是得到原来的一个版本和新的一个版本 , 然后对于这个点减去两个版本的差值就行了 .

注意这个版本是意味着这整条重链的版本 , 因为我们要求的是链顶端所有子树最后贡献出来的 dp 值.

询问的话直接询问 1 所在重链的答案 . 然后取它选与不选的 max 就行了.

注意线段树中的询问 , 最好别写单位矩阵分别乘上左右两边的写法 , 常数会陡增 !!

但还是介绍一下单位矩阵 ... (自己推得qwq) 对角线全都是 0 , 其他位置全是 .

也就是 [0 0 \- 0]

最后时间复杂度就是 O(nlog2n) 的... 有点恐怖 , 但跑的还是算快的 . 矩阵乘法那里有 8 的常数有点伤 .

代码

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) using namespace std; bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar() ) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar() ) x = (x<<1) + (x<<3) + (ch ^ '0'); return x * fh; } void File() { #ifdef zjp_shadow freopen ("P4643.in", "r", stdin); freopen ("P4643.out", "w", stdout); #endif } const int N = 1e5 + 1e3, Lim = 2, inf = 0x7f7f7f7f; struct Matrix { int a[Lim + 1][Lim + 1]; Matrix () { Set(a, 0); } void Unit() { For (i, 1, Lim) For (j, 1, Lim) a[i][j] = (i == j) ? 0 : -inf; } } ; inline Matrix operator * (Matrix a, Matrix b) { Matrix res; For (i, 1, Lim) For (j, 1, Lim) For (k, 1, Lim) chkmax(res.a[i][j], a.a[i][k] + b.a[k][j]); return res; } void Out(Matrix a) { For (i, 1, Lim) For (j, 1, Lim) printf ("%d%c", a.a[i][j], j == jend ? '\n' : ' '); putchar ('\n'); } vector<int> G[N]; int sz[N], son[N], fa[N]; void Dfs_Init(int u, int from = 0) { sz[u] = 1; fa[u] = from; for (int v : G[u]) if (v ^ from) { Dfs_Init(v, u), sz[u] += sz[v]; if (sz[v] > sz[son[u]]) son[u] = v; } } int dfn[N], id[N], top[N], back[N]; void Dfs_Part(int u) { static int clk = 0; id[dfn[u] = ++ clk] = u; top[u] = son[fa[u]] == u ? top[fa[u]] : u; back[top[u]] = u; if (son[u]) Dfs_Part(son[u]); for (int v : G[u]) if ((v ^ fa[u]) && (v ^ son[u])) Dfs_Part(v); } int dp[N][2], val[N]; void Dp_Pre(int u) { int summax = 0, sum0 = val[u]; for (int v : G[u]) if (v ^ fa[u]) Dp_Pre(v), sum0 += dp[v][0], summax += max(dp[v][0], dp[v][1]); dp[u][0] = summax, dp[u][1] = sum0; } Matrix sub[N]; #define lson o << 1, l, mid #define rson o << 1 | 1, mid + 1, r struct Segment_Tree { Matrix Adv[N << 2]; inline void push_up(int o) { Adv[o] = Adv[o << 1] * Adv[o << 1 | 1]; } void Build(int o, int l, int r) { if (l == r) { int u = id[l], summax = 0, sum0 = val[u]; for (int v : G[u]) if ((v ^ fa[u]) && (v ^ son[u])) summax += max(dp[v][0], dp[v][1]), sum0 += dp[v][0]; Adv[o].a[1][1] = Adv[o].a[1][2] = summax; Adv[o].a[2][1] = sum0; sub[u] = Adv[o]; return ; } int mid = (l + r) >> 1; Build(lson); Build(rson); push_up(o); } void Update(int o, int l, int r, int up) { if (l == r) { Adv[o] = sub[id[l]]; return ; } int mid = (l + r) >> 1; if (up <= mid) Update(lson, up); else Update(rson, up); push_up(o); } Matrix Query(int o, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return Adv[o]; int mid = (l + r) >> 1; /* Matrix tmp; tmp.Unit(); if (ql <= mid) tmp = tmp * Query(lson, ql, qr); if (qr > mid) tmp = tmp * Query(rson, ql, qr); */ if(qr <= mid) return Query(lson, ql, qr); if(ql > mid) return Query(rson, ql, qr); return Query(lson, ql, qr) * Query(rson, ql, qr); } } T; int n, m; inline void Update(int pos, int uv) { sub[pos].a[2][1] += - val[pos] + uv, val[pos] = uv; Matrix bef, aft; while (pos) { bef = T.Query(1, 1, n, dfn[top[pos]], dfn[back[top[pos]]]); T.Update(1, 1, n, dfn[pos]); aft = T.Query(1, 1, n, dfn[top[pos]], dfn[back[top[pos]]]); pos = fa[top[pos]]; sub[pos].a[1][2] = (sub[pos].a[1][1] += - max(bef.a[1][1], bef.a[2][1]) + max(aft.a[1][1], aft.a[2][1])); sub[pos].a[2][1] += - bef.a[1][1] + aft.a[1][1]; } } inline int Query() { Matrix tmp = T.Query(1, 1, n, dfn[1], dfn[back[top[1]]]); return max(tmp.a[1][1], tmp.a[2][1]); } int main () { File(); n = read(); m = read(); For (i, 1, n) val[i] = read(); For (i, 1, n - 1) { int u = read(), v = read(); G[u].push_back(v); G[v].push_back(u); } Dfs_Init(1); Dfs_Part(1); Dp_Pre(1); T.Build(1, 1, n); while (m --) { int x = read(), y = read(); Update(x, y); printf ("%d\n", Query()); } //cerr << (double) clock() / CLOCKS_PER_SEC << endl; return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9200826.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(513)  评论(2编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示