学习了一下动态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\) 修改才能维护好。

最后放一下代码:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using std::min;
using std::max;
using std::swap;
using std::vector;
typedef double db;
typedef long long ll;
#define pb(A) push_back(A)
#define pii std::pair<int,int>
#define all(A) A.begin(),A.end()
#define mp(A,B) std::make_pair(A,B)
const int N=1e5+5;
const int inf=1e9;
#define ls x<<1
#define rs x<<1|1
#define lss ls,l,mid,ql,qr
#define rss rs,mid+1,r,ql,qr int tot,dfn[N],top[N],end[N];
int n,m,v[N],sze[N],son[N],f[N][2];
int cnt,head[N],g[N][2],fa[N],fs[N]; struct Edge{
int to,nxt;
}edge[N<<1]; void add(int x,int y){
edge[++cnt].to=y;
edge[cnt].nxt=head[x];
head[x]=cnt;
} struct Mat{
int a[3][3];
Mat(){memset(a,0xcf,sizeof a);}
friend Mat operator*(Mat x,Mat y){
Mat z;
for(int i=1;i<=2;i++)
for(int k=1;k<=2;k++)
for(int j=1;j<=2;j++)
z.a[i][j]=max(x.a[i][k]+y.a[k][j],z.a[i][j]);
return z;
}
}sum[N<<2],val[N]; int getint(){
int X=0,w=0;char ch=getchar();
while(!isdigit(ch))w|=ch=='-',ch=getchar();
while( isdigit(ch))X=X*10+ch-48,ch=getchar();
if(w) return -X;return X;
} void dfs(int now){
sze[now]=1;
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;
if(sze[to]) continue;
fa[to]=now;dfs(to);
sze[now]=sze[to];
son[now]=sze[son[now]]>sze[to]?son[now]:to;
}
} void dfs(int now,int low){
dfn[now]=++tot;top[now]=low;fs[tot]=now;
if(son[now]) dfs(son[now],low);
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;
if(dfn[to]) continue;
dfs(to,to);
g[now][0]+=max(f[to][0],f[to][1]);
g[now][1]+=f[to][0];
} if(son[now]) end[now]=end[son[now]];
else end[now]=now;
g[now][1]+=v[now];
f[now][0]=g[now][0]+max(f[son[now]][0],f[son[now]][1]);
f[now][1]=g[now][1]+f[son[now]][0];
} void pushup(int x){
sum[x]=sum[ls]*sum[rs];
} void build(int x,int l,int r){
if(l==r) return sum[x]=val[fs[l]],void();
int mid=l+r>>1; build(ls,l,mid),build(rs,mid+1,r);
pushup(x);
} void init(){
for(int i=1;i<=n;i++)
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;
build(1,1,n);
} Mat query(int x,int l,int r,int ql,int qr){
if(ql<=l and r<=qr) return sum[x];
int mid=l+r>>1;
if(qr<=mid) return query(lss);
if(ql>mid) return query(rss);
return query(lss)*query(rss);
} Mat ask(int x){
return query(1,1,n,dfn[top[x]],dfn[end[x]]);
} void modify(int x,int l,int r,int ql,int qr){
if(l==r) return sum[x]=val[fs[l]],void();
int mid=l+r>>1;
ql<=mid?modify(lss):modify(rss);
pushup(x);
} void change(int x,int y){
val[x].a[2][1]+=y-v[x]; v[x]=y;
Mat pre,nxt;
while(x){
pre=ask(x);
modify(1,1,n,dfn[x],dfn[x]);
nxt=ask(x);
x=fa[top[x]];
val[x].a[1][1]+=max(nxt.a[1][1],nxt.a[2][1])-max(pre.a[1][1],pre.a[2][1]);
val[x].a[1][2]=val[x].a[1][1];
val[x].a[2][1]+=nxt.a[1][1]-pre.a[1][1];
}
} signed main(){
n=getint(),m=getint();
for(int i=1;i<=n;i++) v[i]=getint();
for(int i=1;i<n;i++){
int x=getint(),y=getint();
add(x,y),add(y,x);
} dfs(1),dfs(1,1),init();
while(m--){
int x=getint(),y=getint();
change(x,y);
Mat ans=ask(1);
printf("%d\n",max(ans.a[1][1],ans.a[2][1]));
} return 0;
}

然后两道例题:

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. 线程中的setDaemon方法

    setDaemon方法必须在start方法前定义.t1.setDaemon(True),该语句的意思是:将主线程A设置为子线程t1的守护线程.也就是在执行程序时,t1会随着主线程A的退出而退出,不论t ...

  2. WEB服务器搭建(Apache+Tomcat+eclipse)

    1.下载xampp安装,选择Apache+MySQL+Tomcat 官方下载链接:https://www.apachefriends.org/zh_cn/download.html 2.下载安装jav ...

  3. Codeforces Educational Codeforces Round 44 (Rated for Div. 2) E. Pencils and Boxes

    Codeforces Educational Codeforces Round 44 (Rated for Div. 2) E. Pencils and Boxes 题目连接: http://code ...

  4. [Solution] JZOJ-5818 做运动

    [Solution] JZOJ-5818 做运动 Time Limits:2000ms Memory Limits:524288KB Description 一天,Y 君在测量体重的时候惊讶的发现,由 ...

  5. ICO图标下载地址

    http://findicons.com/ http://www.iconfont.cn/

  6. Windows平台最方便最易用的法语输入法

    原文:http://wenwen.sogou.com/z/q1700007921.htm 对于XP,在“控制面板”中选择“输入法区域设置”,单击“更改”,出现一个“设置”框:选择“添加”,然后选择“法 ...

  7. maven项目打包成war包发布到tomcat下...

    分为两种情况: 1.当你的项目中没有前端页面时,直接右键项目--Export--选择web下的warFile--选择目录到tomcat安装目录下的webapps下即可访问. 2.当项目中有前端页面时 ...

  8. MIPS汇编指令集

    MIPS有三种指令格式: R型 6 5 5 5 5 6 op rs rt rd shamt funct 功能:寄存器-寄存器ALU操作 (算术运算,逻辑运算) I型 6 5 5 16 op rs rt ...

  9. #221 Iterate Through an Array with a For Loop

    迭代输出一个数组的每个元素是 JavaScript 中的常见需求, for 循环可以做到这一点. 下面的代码将输出数组 arr 的每个元素到控制台: var arr = [10,9,8,7,6];fo ...

  10. django查询数据库无法过滤月份的解决

    我试过,当settings里的:USE_TZ = False时也可以查询,但是数据库里的时间就会显示错的 解决方法是可以再终端输入 mysql_tzinfo_to_sql /usr/share/zon ...