题目链接

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. wap2app(五)-- 微信授权登录以及踩过的坑

    应用场景是:用Hbuilder打包app,在app中点击微信授权登录或者某一操作,调起微信授权登录,用户授权后拿到用户信息. 一.登录插件配置 先配置微信登录参数 appid和appsecret,在m ...

  2. Docker Data Center系列(一)- 快速搭建云原生架构的实践环境

    本系列文章演示如何快速搭建一个简单的云原生架构的实践环境. 基于这个基础架构,可以持续部署微服务架构的应用栈,演练敏捷开发过程,提升DevOps实践能力. 1 整体规划 1.1 拓扑架构 1.2 基础 ...

  3. hadoop1.0 和 Hadoop 2.0 的区别

    1.Hadoop概述 在Google三篇大数据论文发表之后,Cloudera公司在这几篇论文的基础上,开发出了现在的Hadoop.但Hadoop开发出来也并非一帆风顺的,Hadoop1.0版本有诸多局 ...

  4. SQL Sever AlwaysOn的数据同步原理

    1. SQL Server AlwaysOn数据同步基本工作 AlwaysOn 副本同步需要完成三件事: 1.把主副本上发生的数据变化记录下来. 2.把这些记录传输到各个辅助副本. 3.把数据变化在辅 ...

  5. SQLServer之存储过程简介

    存储过程定义 存储的过程 (存储过程(数据库引擎)) 是存储在数据库中的可执行对象. 存储过程分类 系统存储过程   系统存储过程是 SQL Server系统自身提供的存储过程,可以作为命令执行各种操 ...

  6. SQL Server -- 回忆笔记(五):T-SQL编程,系统变量,事务,游标,触发器

    SQL Server -- 回忆笔记(五):T-SQL编程,系统变量,事务,游标,触发器 1. T-SQL编程 (1)声明变量 declare @age int (2)为变量赋值 (3)while循环 ...

  7. Windows 下安装drozer(Windows 10),连接手机(红米note4X)

    Windows 下安装drozer(Windows 10),连接手机(红米note4X) 首先下载drozer(http://mwr.to/drozer). 红米手机开发者模式 遇到第一个问题,红米手 ...

  8. the security settings could not be applied to the database(mysql安装error)【简记】

    在安装mysql时,出现“The security settings could not be applied to the database because the connection has f ...

  9. es crul查询(一)

    C:\Users\Administrator>elasticdump --input=D:\test --output=http://localhost:9200/logs_apipki_201 ...

  10. Linux+Shell常用命令总结

    因为自己不经常使用linux的命令行工具,但是mac的终端还是经常使用的,有些命令总是要想一会或者百度一下才知道怎么用,抽时间整理了一下常用的命令,作为笔记. 常用命令 查看文件操作: ls :列出当 ...