【模板】"动态 DP"&动态树分治

第一道动态\(DP\)的题,只会用树剖来做,全局平衡二叉树什么的就以后再学吧

所谓动态\(DP\),就是在原本的\(DP\)求解的问题上加上修改操作,从而使得问题变成动态的问题

这道题的问题就是普通的树形\(DP\)上加上了修改点权的操作

题意:

给定一棵 \(n\) 个点的树。\(i\) 号点的点权为 \(a_i\)。有 \(m\) 次操作,每次操作给定 \(u\),\(w\),表示修改点 \(u\) 的权值为 \(w\)。你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

考虑最暴力的做法,每次修改点权之后暴力求解,复杂度\(O(nm)\)

稍微做一点优化,可以发现每次修改值之后,只有修改的点到根的路径上的点的\(dp\)值会发生改变,所以只更新路径上的点的\(dp\)值就好了,但是如果树退化成链的话,复杂度还是\(O(nm)\)的

令\(f[u][0]\)为不选择\(u\)点的情况下以\(u\)为根的子树的独立集的最大值,\(f[u][1]\)为选择\(u\)点的情况下以\(u\)为根的子树的独立集的最大值

先把转移方程列出来:

\[\begin{cases}f[u][0] = \sum_{v\in son_u}\max(f[v][0],f[v][1]) \\ f[u][1] = A[u] + \sum_{v\in son_u} f[v][0] \end{cases}
\]

如果对原树进行轻重链剖分,对于每个点只存在一个重儿子

那么我们令\(\begin{cases} g[u][0]=\sum_{v\in lson_u}\max(f[v][0],f[v][1])\\ g[u][1] = A[u] + \sum_{v\in lson_u} f[v][0]\end{cases}\)

其中\(v\in lson_u\)表示\(v\)是\(u\)的轻儿子

然后我们单独考虑重儿子\(hv\),可以得到\(\begin{cases}f[u][0] = g[u][0] + max(f[hv][0],f[hv][1]) \\ f[u][1] = g[u][1] + f[hv][0]\end{cases}\)

这样的\(dp\)转移一般可以用矩阵来表示,然后用矩阵连乘来把转移矩阵乘起来快速得到答案,但是这里的转移和一般的转移不同,我们考虑一个新的矩阵运算:

\[\left[
\begin{array}{c}
a_{11}& a_{12}\\
\end{array}
\right ]

\cdot

\left[
\begin{array}{cc}
b_{11}& b_{12}\\
b_{21}& b_{22}\\
\end{array}
\right ]

=

\left[

\begin{array}{c}
\max(a_{11} + b_{11},a_{12} + b_{21}) &
\max(a_{11} + b_{12},a_{12} + b_{22})
\end{array}

\right]
\]

那么我们就可以用矩阵的形式来表示转移了:

\[\left[

\begin{array}{c}
f[i][0] & f[i][1]
\end{array}

\right]
=

\left[
\begin{array}{c}
f[hv][0]& f[hv][1]\\
\end{array}
\right]

\cdot

\left[
\begin{array}{cc}
g[i][0]& g[i][1]\\
g[i][0]& -\infty\\
\end{array}
\right]

\]

由于重儿子不计算在这个转移矩阵内,所以每次修改的时候我们从下往上,每次到重链顶部的时候,跳一下轻链,只要修改这个点的转移矩阵就好了,链数不会超过\(\log n\),而这个转移矩阵的区间乘积可以用线段树来维护,所以总复杂度是\(O(n\log^2 n)\)

  1. //#pragma GCC optimize("O3")
  2. //#pragma comment(linker, "/STACK:1024000000,1024000000")
  3. #include<bits/stdc++.h>
  4. using namespace std;
  5. function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
  6. const int MAXN = 1e5+7;
  7. struct Matrix{
  8. int mat[2][2];
  9. Matrix(){ memset(mat,-0x3f,sizeof(mat)); }
  10. Matrix operator * (const Matrix &rhs) const{
  11. Matrix ret;
  12. for(int i = 0; i < 2; i++)
  13. for(int j = 0; j < 2; j++)
  14. for(int k = 0; k < 2; k++)
  15. ret.mat[i][j] = max(ret.mat[i][j],mat[i][k] + rhs.mat[k][j]);
  16. return ret;
  17. }
  18. };
  19. int n, m, par[MAXN], A[MAXN];
  20. int top[MAXN], dep[MAXN], sz[MAXN], dfn[MAXN], rdfn[MAXN], son[MAXN], tail[MAXN], idx;
  21. vector<int> G[MAXN];
  22. Matrix w[MAXN];
  23. struct SegmentTree{
  24. Matrix M[MAXN<<2];
  25. int l[MAXN<<2], r[MAXN<<2];
  26. #define ls(rt) rt << 1
  27. #define rs(rt) rt << 1 | 1
  28. void pushup(int rt){ M[rt] = M[rs(rt)] * M[ls(rt)]; }
  29. void build(int L, int R, int rt = 1){
  30. l[rt] = L; r[rt] = R;
  31. if(L+1==R){
  32. M[rt] = w[rdfn[L]];
  33. return;
  34. }
  35. int mid = (L + R) >> 1;
  36. build(L,mid,ls(rt)); build(mid,R,rs(rt));
  37. pushup(rt);
  38. }
  39. void update(int pos, Matrix &MT, int rt = 1){
  40. if(l[rt] + 1 == r[rt]){
  41. M[rt] = MT;
  42. return;
  43. }
  44. int mid = (l[rt] + r[rt]) >> 1;
  45. if(pos<mid) update(pos,MT,ls(rt));
  46. else update(pos,MT,rs(rt));
  47. pushup(rt);
  48. }
  49. Matrix query(int L, int R, int rt = 1){
  50. if(L<=l[rt] and r[rt]<=R) return M[rt];
  51. int mid = (l[rt] + r[rt]) >> 1;
  52. if(mid <= L) return query(L,R,rs(rt));
  53. else if(R <= mid) return query(L,R,ls(rt));
  54. else return query(L,R,rs(rt)) * query(L,R,ls(rt));
  55. }
  56. }ST;
  57. void dfs1(int u, int fa){
  58. dep[u] = dep[par[u] = fa] + 1;
  59. sz[u] = 1; son[u] = 0;
  60. for(int v : G[u]){
  61. if(v==fa) continue;
  62. dfs1(v,u);
  63. sz[u] += sz[v];
  64. if(sz[v] > sz[son[u]]) son[u] = v;
  65. }
  66. }
  67. int f[MAXN][2];
  68. void dfs2(int u, int tp){
  69. dfn[u] = ++idx;
  70. rdfn[idx] = u;
  71. top[u] = tp;
  72. tail[tp] = u;
  73. w[u].mat[0][0] = w[u].mat[1][0] = 0;
  74. w[u].mat[0][1] = A[u];
  75. f[u][0] = 0;
  76. f[u][1] = A[u];
  77. if(son[u]){
  78. dfs2(son[u],tp);
  79. f[u][0] += max(f[son[u]][0],f[son[u]][1]);
  80. f[u][1] += f[son[u]][0];
  81. }
  82. for(int v : G[u]){
  83. if(v==par[u] or v==son[u]) continue;
  84. dfs2(v,v);
  85. f[u][0] += max(f[v][0],f[v][1]);
  86. f[u][1] += f[v][0];
  87. w[u].mat[0][0] += max(f[v][0],f[v][1]);
  88. w[u].mat[1][0] += max(f[v][0],f[v][1]);
  89. w[u].mat[0][1] += f[v][0];
  90. }
  91. }
  92. void update(int u, int x){
  93. w[u].mat[0][1] += x - A[u];
  94. A[u] = x;
  95. while(u){
  96. Matrix bef = ST.query(dfn[top[u]],dfn[tail[top[u]]]+1);
  97. ST.update(dfn[u],w[u]);
  98. Matrix aft = ST.query(dfn[top[u]],dfn[tail[top[u]]]+1);
  99. u = par[top[u]];
  100. w[u].mat[0][0] -= max(bef.mat[0][0],bef.mat[0][1]) - max(aft.mat[0][0],aft.mat[0][1]);
  101. w[u].mat[1][0] = w[u].mat[0][0];
  102. w[u].mat[0][1] -= bef.mat[0][0] - aft.mat[0][0];
  103. }
  104. }
  105. void solve(){
  106. cin >> n >> m;
  107. for(int i = 1; i <= n; i++) cin >> A[i];
  108. for(int i = 1; i < n; i++){
  109. int u, v;
  110. cin >> u >> v;
  111. G[u].push_back(v);
  112. G[v].push_back(u);
  113. }
  114. dfs1(1,0); dfs2(1,1);
  115. ST.build(1,n+1);
  116. while(m--){
  117. int u, x;
  118. cin >> u >> x;
  119. update(u,x);
  120. Matrix ret = ST.query(dfn[1],dfn[tail[1]]+1);
  121. printf("%d\n",max(ret.mat[0][0],ret.mat[0][1]));
  122. }
  123. }
  124. int main(){
  125. #ifndef ONLINE_JUDGE
  126. freopen("Local.in","r",stdin);
  127. freopen("ans.out","w",stdout);
  128. #endif
  129. ____();
  130. solve();
  131. return 0;
  132. }

洛谷P4719 【模板】"动态 DP"&动态树分治的更多相关文章

  1. 洛谷 P2147 [SDOI2008]洞穴勘测 (线段树分治)

    题目链接 题解 早就想写线段树分治的题了. 对于每条边,它存在于一段时间 我们按时间来搞 我们可把一条边看做一条线段 我们可以模拟线段树操作,不断分治下去 把覆盖\(l-r\)这段时间的线段筛选出来, ...

  2. 洛谷P2305 [NOI2014]购票 [DP,树状数组]

    传送门 思路 显然是树形DP,显然是斜率优化,唯一的问题就是该怎么维护凸包. 套路1:树上斜率优化,在没有这题的路程的限制的情况下,可以维护一个单调栈,每次加入点的时候二分它会加到哪里,然后替换并记录 ...

  3. 洛谷P3834 [模板]可持久化线段树1(主席树) [主席树]

    题目传送门 可持久化线段树1(主席树) 题目背景 这是个非常经典的主席树入门题——静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定 ...

  4. 洛谷.3834.[模板]可持久化线段树(主席树 静态区间第k小)

    题目链接 //离散化后范围1~cnt不要错 #include<cstdio> #include<cctype> #include<algorithm> //#def ...

  5. 洛谷 P4755 - Beautiful Pair(主席树+分治+启发式优化)

    题面传送门 wssb,我紫菜 看到这类与最大值统计有关的问题可以很自然地想到分治,考虑对 \([l,r]\) 进行分治,求出对于所有 \(l\le x\le y\le r\) 的点对 \((x,y)\ ...

  6. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  7. 洛谷 P4719 【模板】动态dp【动态dp】

    是动态dp的板子 大致思想就是用g[u]来表示不包含重链转移的dp值,然后用线段树维护重链,这样线段树的根就相当于这条重链的top的真实dp值 每次修改的时候,修改x点会影响到x到根的真实dp值,但是 ...

  8. 洛谷P4719 【模板】动态dp(ddp LCT)

    题意 题目链接 Sol 动态dp板子题.有些细节还没搞懂,待我研究明白后再补题解... #include<bits/stdc++.h> #define LL long long using ...

  9. LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)

    为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...

随机推荐

  1. Solon rpc 之 SocketD 协议 - RPC调用模式

    Solon rpc 之 SocketD 协议系列 Solon rpc 之 SocketD 协议 - 概述 Solon rpc 之 SocketD 协议 - 消息上报模式 Solon rpc 之 Soc ...

  2. Linux 入门教程:00 Background

    Linux 为何物? 就是一个操作系统. Linux 历史: 操作系统始于二十世纪五十年代,当时的操作系统能运行批处理程序.批处理程序不需要用户的交互,它从文件或者穿孔卡片读取数据,然后输出到另外一个 ...

  3. Docker学习笔记之Dockerfile

    Dockerfile的编写格式为<命令><形式参数>,命令不区分大小写,但一般使用大写字母.Docker会依据Dockerfile文件中编写的命令顺序依次执行命令.Docker ...

  4. .NET 5 程序高级调试-WinDbg

    上周和大家分享了.NET 5开源工作流框架elsa,程序跑起来后,想看一下后台线程的执行情况.抓了个进程Dump后,使用WinDbg调试,加载SOS调试器扩展,结果无法正常使用了: 0:000> ...

  5. Linux Clone函数

    Linux Clone函数 之前某一次有过一次面试,问了内核中是怎么创建命名空间的? 下面就来扒一扒clone的精髓,以及如何通过它创建命名空间. 目录 Linux Clone函数 使用clone创建 ...

  6. docker 常用的容器命令

    容器命令 # --name 给容器起名 # -p 端口映射 # -d 后台启动 # -it 交互模式启动 # 交互模式启动 # docker run -it 镜像名/id /bin/bash # do ...

  7. 【排序基础】5、插入排序法 - Insertion Sort

    插入排序法 - Insertion Sort 文章目录 插入排序法 - Insertion Sort 插入排序设计思想 插入排序代码实现 操作:插入排序与选择排序的比较 简单记录-bobo老师的玩转算 ...

  8. LeetCode543.二叉树的直径

    题目 1 class Solution { 2 public: 3 int minimum = INT_MIN; 4 vector<int>res; 5 int diameterOfBin ...

  9. go语言循环变量

    阅读go语言圣经第五章第六节介绍到了捕获迭代变量 package main import ( "fmt" ) func main() { var lis []func() for ...

  10. 华为刘腾:华为终端云Cassandra运维实践分享

    点击此处观看完整活动视频 各位线上的嘉宾朋友大家好,我是来自华为消费者BG云服务部的刘腾,我今天给大家分享的主题是华为终端云Cassandra运维实践.和前面王峰老师提到的Cassandra在360中 ...