题目链接

Luogu P4643

题解

猫锟在WC2018讲的黑科技——动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样。(这道题也是PPT中的例题)

动态DP的一个套路是把DP转移方程写成矩阵乘法,然后用线段树(树上的话就是树剖)维护矩阵,这样就可以做到修改了。

注意这个“矩阵乘法”不一定是我们常见的那种乘法和加法组成的矩阵乘法。设\(A * B = C\),常见的那种矩阵乘法是这样的:

\[C_{i, j} = \sum_{k = 1}^{n} A_{i, k} * B_{k, j}
\]

而这道题中的矩阵乘法是这样的:

\[C_{i, j} = \max_{k = 1}^{n} (A_{i, k} + B_{k, j})
\]

这就相当于常见矩阵乘法中的加法变成了max,乘法变成了加法。类似于乘法和加法的五种运算律,这两种变化也满足“加法交换律”、“加法结合律”、“max交换律”、“max结合律”和“加法分配律“。那么这种矩阵乘法显然也满足矩阵乘法结合律,就像正常的矩阵乘法一样,可以用线段树维护。

接下来我们来构造矩阵。首先研究DP方程。

就像“没有上司的舞会”一样,\(f_{i, 0}\)表示子树\(i\)中不选\(i\)的最大权独立集大小,\(f_{i, 1}\)表示子树\(i\)中选\(i\)的最大权独立集大小。

但这是动态DP,我们需要树链剖分。假设我们已经完成了树链剖分,剖出来的某条重链看起来就像这样,右边的是在树上深度较大的点:

此时,比这条重链的top深度大且不在这条重链上的点的DP值都是已经求出来的(这可以做到)。我们把它们的贡献,都统一于它们在这条重链上对应的那个祖先上。

具体来说,设\(g_{i, 0}\)表示不选\(i\)时,\(i\)不在链上的子孙的最大权独立集大小,\(g_{i, 1}\)表示选\(i\)时,\(i\)不在链上的子孙再加上\(i\)自己的最大权独立集大小。

假如\(i\)右面的点是\(i + 1\), 那么可以得出:

\[f_{i, 0} = g_{i, 0} + \max(f_{i + 1, 0}, f_{i + 1, 1})
\]

\[f_{i, 1} = g_{i, 1} + f_{i + 1, 0}
\]

矩阵也就可以构造出来了:

\[\begin{bmatrix}g_{i, 0} & g_{i, 0} \\g_{i, 1} & 0\end{bmatrix} * \begin{bmatrix}f_{i + 1, 0} \\ f_{i + 1, 1}\end{bmatrix} = \begin{bmatrix}f_{i, 0} \\ f_{i, 1}\end{bmatrix}
\]

读者可以动笔验证一下。(注意我们在这里用的“新矩阵乘法”的规则:原来的乘变成加,加变成取max。)

那么基本思路就很清楚了:树剖,维护区间矩阵乘积。修改的时候,对于被修改节点到根节点路径上的每个重链(由下到上),先进行单点修改,然后求出这条重链的\(top\)在修改之后的\(f\)值,然后继续修改top所在重链。

每次答案就是节点\(1\)的\(f\)值。

代码

代码略丑,见谅……

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x){
char c;
bool op = 0;
while(c = getchar(), c < '0' || c > '9')
if(c == '-') op = 1;
x = c - '0';
while(c = getchar(), c >= '0' && c <= '9')
x = x * 10 + c - '0';
if(op) x = -x;
}
template <class T>
void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar('0' + x % 10);
} const int N = 100005;
int n, m, a[N];
int ecnt, adj[N], nxt[2*N], go[2*N];
int fa[N], son[N], sze[N], top[N], idx[N], pos[N], tot, ed[N];
ll f[N][2]; struct matrix {
ll g[2][2];
matrix(){
memset(g, 0, sizeof(g));
}
matrix operator * (const matrix &b) const {
matrix c;
for(int i = 0; i < 2; i++)
for(int j = 0; j < 2; j++)
for(int k = 0; k < 2; k++)
c.g[i][j] = max(c.g[i][j], g[i][k] + b.g[k][j]);
return c;
}
} val[N], data[4*N]; void add(int u, int v){
go[++ecnt] = v;
nxt[ecnt] = adj[u];
adj[u] = ecnt;
} void init(){
static int que[N];
que[1] = 1;
for(int ql = 1, qr = 1; ql <= qr; ql++)
for(int u = que[ql], e = adj[u], v; e; e = nxt[e])
if((v = go[e]) != fa[u])
fa[v] = u, que[++qr] = v;
for(int qr = n, u; qr; qr--){
sze[u = que[qr]]++;
sze[fa[u]] += sze[u];
if(sze[u] > sze[son[fa[u]]])
son[fa[u]] = u;
}
for(int ql = 1, u; ql <= n; ql++)
if(!top[u = que[ql]]){
for(int v = u; v; v = son[v])
top[v] = u, idx[pos[v] = ++tot] = v;
ed[u] = tot;
}
for(int qr = n, u; qr; qr--){
u = que[qr];
f[u][1] = max(0, a[u]);
for(int e = adj[u], v; e; e = nxt[e])
if(v = go[e], v != fa[u]){
f[u][0] += max(f[v][0], f[v][1]);
f[u][1] += f[v][0];
}
}
} void build(int k, int l, int r){
if(l == r){
ll g0 = 0, g1 = a[idx[l]];
for(int u = idx[l], e = adj[u], v; e; e = nxt[e])
if((v = go[e]) != fa[u] && v != son[u])
g0 += max(f[v][0], f[v][1]), g1 += f[v][0];
data[k].g[0][0] = data[k].g[0][1] = g0;
data[k].g[1][0] = g1;
val[l] = data[k];
return;
}
int mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
data[k] = data[k << 1] * data[k << 1 | 1];
}
void change(int k, int l, int r, int p){
if(l == r){
data[k] = val[l];
return;
}
int mid = (l + r) >> 1;
if(p <= mid) change(k << 1, l, mid, p);
else change(k << 1 | 1, mid + 1, r, p);
data[k] = data[k << 1] * data[k << 1 | 1];
}
matrix query(int k, int l, int r, int ql, int qr){
if(ql <= l && qr >= r) return data[k];
int mid = (l + r) >> 1;
if(qr <= mid) return query(k << 1, l, mid, ql, qr);
if(ql > mid) return query(k << 1 | 1, mid + 1, r, ql, qr);
return query(k << 1, l, mid, ql, qr) * query(k << 1 | 1, mid + 1, r, ql, qr);
}
matrix ask(int u){
return query(1, 1, n, pos[top[u]], ed[top[u]]);
}
void path_change(int u, int x){
val[pos[u]].g[1][0] += x - a[u];
a[u] = x;
matrix od, nw;
while(u){
od = ask(top[u]);
change(1, 1, n, pos[u]);
nw = ask(top[u]);
u = fa[top[u]];
val[pos[u]].g[0][0] += max(nw.g[0][0], nw.g[1][0]) - max(od.g[0][0], od.g[1][0]);
val[pos[u]].g[0][1] = val[pos[u]].g[0][0];
val[pos[u]].g[1][0] += nw.g[0][0] - od.g[0][0];
}
} int main(){ read(n);
read(m);
for(int i = 1; i <= n; i++) read(a[i]);
for(int i = 1, u, v; i < n; i++)
read(u), read(v), add(u, v), add(v, u);
init();
build(1, 1, n);
int u, x;
matrix t;
while(m--){
read(u), read(x);
path_change(u, x);
t = ask(1);
write(max(t.g[0][0], t.g[1][0])), enter;
} return 0;
}

Luogu P4643 【模板】动态dp的更多相关文章

  1. [luogu 4719][模板]动态dp

    传送门 Solution \(f_{i,0}\) 表示以i节点为根的子树内,不选i号节点的最大独立集 \(f_{i,1}\)表示以i节点为根的子树内,选i号节点的最大独立集 \(g_{i,0}\) 表 ...

  2. [模板] 动态dp

    用途 对于某些树形dp(目前只会树上最大权独立集或者类似的),动态地修改点权,并询问修改后的dp值 做法(树剖版) 以最大权独立集为例 设$f[x][0/1]$表示x选不选,这棵子树的最大权独立集大小 ...

  3. 【洛谷】P4643 【模板】动态dp

    题解 在冬令营上听到冬眠的东西,现在都是板子了猫锟真的是好毒瘤啊(雾) (立个flag,我去thusc之前要把WC2018T1乱搞过去= =) 好的,我们可以参考猫锟的动态动态dp的课件,然后你发现你 ...

  4. LG4719 【模板】动态dp 及 LG4751 动态dp【加强版】

    题意 题目描述 给定一棵\(n\)个点的树,点带点权. 有\(m\)次操作,每次操作给定\(x,y\),表示修改点\(x\)的权值为\(y\). 你需要在每次操作之后求出这棵树的最大权独立集的权值大小 ...

  5. 洛谷4719 【模板】动态dp

    题目:https://www.luogu.org/problemnew/show/P4719 关于动态DP似乎有猫锟的WC2018论文,但找不见:还是算了. http://immortalco.blo ...

  6. 洛谷P4719 【模板】"动态 DP"&动态树分治

    [模板]"动态 DP"&动态树分治 第一道动态\(DP\)的题,只会用树剖来做,全局平衡二叉树什么的就以后再学吧 所谓动态\(DP\),就是在原本的\(DP\)求解的问题上 ...

  7. 【模板】动态 DP

    luogu传送门. 最近学了一下动态dp,感觉没有想象的难. 动态DP simple的DP是这样的: 给棵树,每个点给个权值,求一下最大权独立集. 动态DP是这样的: 给棵树,每个点给个权值还到处改, ...

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

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

  9. 「LGP4719【模板】动态dp」

    题目 尽管知道这个东西应该不会考了,但是还是学一学吧 哎要是去年noip之前学该多好 动态\(dp\)就是允许修改的一个\(dp\),比如这道题,我们都知道这是一个树上最大点权独立集 众所周知方程长这 ...

随机推荐

  1. 小米8 探索版 屏幕指纹版超简单卡刷开发版获取Root权限的教程

    小米的手机不同手机型号通常情况下miui官网都提供两个不同的系统,分别是稳定版和开发版,稳定版没有提供ROOT超级权限管理,开发版中就开启了ROOT超级权限,在很多工作的时候我们需要使用的一些功能强大 ...

  2. Error occurred during initialization of VM Could not reserve enough space for 2097152KB object heap

    ionic build Android后的报错问题 ionic 升级了splashscreen和statusbar的插件后,执行ionic build android会一直报打包错误.原因是过低的An ...

  3. Android 获取 上下文环境参数 getResources

    1----context.getResources().getConfiguration().orientation;//获取屏幕方向int类型,1:portrait,2:landscape 2--- ...

  4. 时序数据库InfluxDB安装及使用

    时序数据库InfluxDB安装及使用 1 安装配置 安装 wget https://dl.influxdata.com/influxdb/releases/influxdb-1.3.1.x86_64. ...

  5. 解决vs启动出现“cannot find one or more components .Please reinstall the application”

    参考下文: https://blog.csdn.net/novice_growth/article/details/71627395

  6. c/c++ 网络编程 bind函数

    网络编程 bind函数 bind的作用是确定端口号. 正常处理都是先bind,然后listen 如果不bind,直接listen,会是什么结果? 内核会自动随机分配一个端口号 例子: #include ...

  7. Swift代理的使用

    Swift代理的使用 协议规定了用来实现某一特定功能所必需的方法和属性. 任意能够满足协议要求的类型被称为遵循(conform)这个协议. 类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议 ...

  8. Codechef Bear and Clique Distances

    题目:Bear and Clique Distances 描述:共有N个点,前1—K个点任意两点之间有一条无向边,边的权值为X,再任意给M条边(u,v,w)(不重复),求任意一点到其余各点的最短路. ...

  9. day 6 - 1 知识点小节

    python2 与 python3 的区别 1. print.input.生成器 #python2 print() print 'abc' range() xrange() 生成器 raw_input ...

  10. Google Chrome等浏览器不允许关闭点击跟踪??

    hrome.Safari.Opera 和 Microsoft Edge 的新版本将不再允许用户关闭“链接审计( hyperlink auditing)”的功能.链接审计是一项 HTML 标准,被用于跟 ...