学习了一下动态DP

问题的来源:

给定一棵 \(n\) 个节点的树,点有点权,有 \(m\) 次修改单点点权的操作,回答每次操作之后的最大带权独立集大小。

首先一个显然的 \(O(nm)\) 的做法就是每次做一遍树形DP(这也是我在noip考场上唯一拿到的部分分),直接考虑如何优化这个东西。

简化一下问题,假如这棵树是一条链,那就变得很简单了,可以直接拿线段树维护矩阵加速。

可是如果每个点不止有一个儿子呢?

我们首先树剖一下。

设 \(g[i][0]=\sum\limits_{j\in lightson} \max(f[j][0],f[j][1])\)

\(g[i][1]=a[i]+\sum\limits_{j\in lightson} f[j][0]\)

即 \(g[i][0]\) 表示 \(i\) 的所有轻儿子的 \(\max(f[j][0],f[j][1])\) 之和,\(g[i][1]\) 表示 \(i\) 的所有轻儿子的 \(g[j][0]\) 之和与 \(a[i]\) 的和。

那转移方程就可以改写为 \(f[i][0]=\max(g[i][0]+f[son[i]][0],g[i][0]+f[son[i]][1])\)

\(f[i][1]=g[i][1]+f[son[i]][0]\)

这就可以放在线段树上维护矩阵了。

即每个点维护一个 \(\quad\begin{matrix}g[i][0]&g[i][0]\\-\infty&g[i][1]\end{matrix}\)。然后在线段树上维护连乘积就好。

还有一点就是修改的时候,要一直跳 \(top\),可以这样理解:假设当前更改了点 \(x\) 的点权,那么就会改变 \(f[top[x]]\) 的值,紧接着就会影响 \(g[fa[top[x]]]\) 的值,所以我们要一直向上跳 \(top\) 修改才能维护好。

最后放一下代码:

  1. #pragma GCC optimize(2)
  2. #include<bits/stdc++.h>
  3. using std::min;
  4. using std::max;
  5. using std::swap;
  6. using std::vector;
  7. typedef double db;
  8. typedef long long ll;
  9. #define pb(A) push_back(A)
  10. #define pii std::pair<int,int>
  11. #define all(A) A.begin(),A.end()
  12. #define mp(A,B) std::make_pair(A,B)
  13. const int N=1e5+5;
  14. const int inf=1e9;
  15. #define ls x<<1
  16. #define rs x<<1|1
  17. #define lss ls,l,mid,ql,qr
  18. #define rss rs,mid+1,r,ql,qr
  19. int tot,dfn[N],top[N],end[N];
  20. int n,m,v[N],sze[N],son[N],f[N][2];
  21. int cnt,head[N],g[N][2],fa[N],fs[N];
  22. struct Edge{
  23. int to,nxt;
  24. }edge[N<<1];
  25. void add(int x,int y){
  26. edge[++cnt].to=y;
  27. edge[cnt].nxt=head[x];
  28. head[x]=cnt;
  29. }
  30. struct Mat{
  31. int a[3][3];
  32. Mat(){memset(a,0xcf,sizeof a);}
  33. friend Mat operator*(Mat x,Mat y){
  34. Mat z;
  35. for(int i=1;i<=2;i++)
  36. for(int k=1;k<=2;k++)
  37. for(int j=1;j<=2;j++)
  38. z.a[i][j]=max(x.a[i][k]+y.a[k][j],z.a[i][j]);
  39. return z;
  40. }
  41. }sum[N<<2],val[N];
  42. int getint(){
  43. int X=0,w=0;char ch=getchar();
  44. while(!isdigit(ch))w|=ch=='-',ch=getchar();
  45. while( isdigit(ch))X=X*10+ch-48,ch=getchar();
  46. if(w) return -X;return X;
  47. }
  48. void dfs(int now){
  49. sze[now]=1;
  50. for(int i=head[now];i;i=edge[i].nxt){
  51. int to=edge[i].to;
  52. if(sze[to]) continue;
  53. fa[to]=now;dfs(to);
  54. sze[now]=sze[to];
  55. son[now]=sze[son[now]]>sze[to]?son[now]:to;
  56. }
  57. }
  58. void dfs(int now,int low){
  59. dfn[now]=++tot;top[now]=low;fs[tot]=now;
  60. if(son[now]) dfs(son[now],low);
  61. for(int i=head[now];i;i=edge[i].nxt){
  62. int to=edge[i].to;
  63. if(dfn[to]) continue;
  64. dfs(to,to);
  65. g[now][0]+=max(f[to][0],f[to][1]);
  66. g[now][1]+=f[to][0];
  67. } if(son[now]) end[now]=end[son[now]];
  68. else end[now]=now;
  69. g[now][1]+=v[now];
  70. f[now][0]=g[now][0]+max(f[son[now]][0],f[son[now]][1]);
  71. f[now][1]=g[now][1]+f[son[now]][0];
  72. }
  73. void pushup(int x){
  74. sum[x]=sum[ls]*sum[rs];
  75. }
  76. void build(int x,int l,int r){
  77. if(l==r) return sum[x]=val[fs[l]],void();
  78. int mid=l+r>>1; build(ls,l,mid),build(rs,mid+1,r);
  79. pushup(x);
  80. }
  81. void init(){
  82. for(int i=1;i<=n;i++)
  83. val[i].a[1][1]=val[i].a[1][2]=g[i][0],val[i].a[2][1]=g[i][1],val[i].a[2][2]=-inf;
  84. build(1,1,n);
  85. }
  86. Mat query(int x,int l,int r,int ql,int qr){
  87. if(ql<=l and r<=qr) return sum[x];
  88. int mid=l+r>>1;
  89. if(qr<=mid) return query(lss);
  90. if(ql>mid) return query(rss);
  91. return query(lss)*query(rss);
  92. }
  93. Mat ask(int x){
  94. return query(1,1,n,dfn[top[x]],dfn[end[x]]);
  95. }
  96. void modify(int x,int l,int r,int ql,int qr){
  97. if(l==r) return sum[x]=val[fs[l]],void();
  98. int mid=l+r>>1;
  99. ql<=mid?modify(lss):modify(rss);
  100. pushup(x);
  101. }
  102. void change(int x,int y){
  103. val[x].a[2][1]+=y-v[x]; v[x]=y;
  104. Mat pre,nxt;
  105. while(x){
  106. pre=ask(x);
  107. modify(1,1,n,dfn[x],dfn[x]);
  108. nxt=ask(x);
  109. x=fa[top[x]];
  110. val[x].a[1][1]+=max(nxt.a[1][1],nxt.a[2][1])-max(pre.a[1][1],pre.a[2][1]);
  111. val[x].a[1][2]=val[x].a[1][1];
  112. val[x].a[2][1]+=nxt.a[1][1]-pre.a[1][1];
  113. }
  114. }
  115. signed main(){
  116. n=getint(),m=getint();
  117. for(int i=1;i<=n;i++) v[i]=getint();
  118. for(int i=1;i<n;i++){
  119. int x=getint(),y=getint();
  120. add(x,y),add(y,x);
  121. } dfs(1),dfs(1,1),init();
  122. while(m--){
  123. int x=getint(),y=getint();
  124. change(x,y);
  125. Mat ans=ask(1);
  126. printf("%d\n",max(ans.a[1][1],ans.a[2][1]));
  127. } return 0;
  128. }

然后两道例题:

BZOJ4712 洪水

题意

给出一棵树,点有点权。多次增加某个点的点权,并在某一棵子树中询问:选出若干个节点,使得每个叶子节点到根节点的路径上至少有一个节点被选择,求选出的点的点权和的最小值。

Sol

还是动态DP。

先把DP式子列出来: \(f[i]=\min(a[i],\sum\limits_{j\in son[i]} f[j])\),然后套路设 \(g[i]=\sum\limits_{j\in lightson[i]} f[j]\),于是 \(f[i]=\min(a[i],g[i]+f[son[i]])\)。

又可以写成矩阵相乘的形式:

\[\begin{matrix}f[son[i]]\\0\end{matrix}\times \begin{matrix}g[i]&a[i]\\\infty&0\end{matrix}=\begin{matrix}f[i]\\0\end{matrix}
\]

然后动态DP就行了。

需要注意一点的是在 \(change\) 函数里,\(pre,nxt\) 的矩阵应该是 \(ask(top[x])\) 而不是 \(ask(x)\)。

NOIP2018 保卫王国

题意

给出一棵树,点有点权。每次强制选或不选两个节点,求最小权覆盖集。

Sol

首先有个定理:最小权覆盖集=全集-最大权独立集。然后就是luogu模板题了。

[总结] 动态DP学习笔记的更多相关文章

  1. 动态 DP 学习笔记

    不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果. 动态 DP 主要用于解决一类问题.这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作.举个例子 ...

  2. 动态dp学习笔记

    我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ...

  3. 洛谷4719 【模板】动态dp 学习笔记(ddp 动态dp)

    qwq大概是混乱的一个题. 首先,还是从一个比较基础的想法开始想起. 如果每次暴力修改的话,那么每次就可以暴力树形dp 令\(dp[x][0/1]\)表示\(x\)的子树中,是否选择\(x\)这个点的 ...

  4. 数位DP学习笔记

    数位DP学习笔记 什么是数位DP? 数位DP比较经典的题目是在数字Li和Ri之间求有多少个满足X性质的数,显然对于所有的题目都可以这样得到一些暴力的分数 我们称之为朴素算法: for(int i=l_ ...

  5. DP学习笔记

    DP学习笔记 可是记下来有什么用呢?我又不会 笨蛋你以后就会了 完全背包问题 先理解初始的DP方程: void solve() { for(int i=0;i<;i++) for(int j=0 ...

  6. 树形DP 学习笔记

    树形DP学习笔记 ps: 本文内容与蓝书一致 树的重心 概念: 一颗树中的一个节点其最大子树的节点树最小 解法:对与每个节点求他儿子的\(size\) ,上方子树的节点个数为\(n-size_u\) ...

  7. 斜率优化DP学习笔记

    先摆上学习的文章: orzzz:斜率优化dp学习 Accept:斜率优化DP 感谢dalao们的讲解,还是十分清晰的 斜率优化$DP$的本质是,通过转移的一些性质,避免枚举地得到最优转移 经典题:HD ...

  8. 插头DP学习笔记——从入门到……????

    我们今天来学习插头DP??? BZOJ 2595:[Wc2008]游览计划 Input 第一行有两个整数,N和 M,描述方块的数目. 接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该 ...

  9. 树形$dp$学习笔记

    今天学习了树形\(dp\),一开始浏览各大\(blog\),发现都\(TM\)是题,连个入门的\(blog\)都没有,体验极差.所以我立志要写一篇可以让初学树形\(dp\)的童鞋快速入门. 树形\(d ...

随机推荐

  1. golang 快速排序及二分查找

    二分查找 func main() { arr := []int{0, 1, 2, 3, 4, 5, 6} fmt.Println(BinarySearch(arr, 5)) } func Binary ...

  2. SpringCloud服务注册与发现

    1.介绍对于微服务的治理而言,其核心就是服务的注册和发现.在SpringCloud 中提供了多种服务注册与发现组件:Eureka,Consul,Zookeeper.官方推荐使用Eureka. 说明:E ...

  3. Unity3D编辑器扩展(一)——定义自己的菜单按钮

    Unity3D 引擎的编辑器拥有很强的扩展性,用的好可以帮我们省很多事情.在这里记录下如何去扩展 Unity3D 的编辑器,定制属于我们自己的开发环境. 本篇主要讲解在 Unity3D 引擎的各个窗口 ...

  4. HTTP lab01 做一个简单的测试用 web页面

      做一个简单的测试用 web页面     1.安装httpd服务   yum install httpd   安装完httpd服务后,系统就自动生成了/var/www/html目录     创建一个 ...

  5. uva12307(旋转卡壳)

    省选前练模板系列 #include<iostream> #include<cmath> #include<cstdio> #include<cstring&g ...

  6. 关于CSS层叠机制

    谈到层叠机制,首先我们要知道什么是声明冲突. 声明冲突有三个条件:①多个选择器选中同一个元素:②声明块里的属性相同:③属性的属性值不同.同时满足这三点时就会产生声明冲突.比如下图html代码: < ...

  7. ECharts常用设置记录

    一.配置文档 http://echarts.baidu.com/option.html#title 二.属性配置 1.图表与边框容器距离. grid: { top: '10%', left: '70' ...

  8. C语言+嵌入式SQL+DB2开发经验总结

    1.使用DB2工具将SQC文件预编译成C文件和bnd文件. 命令: db2 prep ***.sqc version * package using * bindfile BLOCKING ALL I ...

  9. 【渗透攻防】千变万化的WebShell

    前言WebShell就是以asp.php.jsp或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门.本篇文章将带大家学习如何获取WebShell,如何隐藏WebShell,有 ...

  10. Android 基本控件相关知识整理

    Android应用开发的一项重要内容就是界面开发.对于用户来说,不管APP包含的逻辑多么复杂,功能多么强大,如果没有提供友好的图形交互界面,将很难吸引最终用户.作为一个程序员如何才能开发出友好的图形界 ...