题目
分析
首先写出朴素的 DP 式: 表示不选 选 点, 的子树内的最大权独立集的权值和。设 是 的儿子(下同),则 用树链剖分将树转化成一个线性结构维护动态 DP 的转移矩阵。具体来说,需要将转移矩阵设计成可以由重链传递的信息,因此设 是 的重儿子,且 于是 转化一下 就变成了喜闻乐见的矩阵“乘法”形式。将常规乘法重新定义 ,于是有 于是我们可以在重链上维护 的转移矩阵的乘积,事实上 数组将将重链周围的轻儿子的信息整合到了该重链上,这样一层一层传递,最终只需要计算以 为顶的重链的转移矩阵乘积,就得到了整个树的最大权独立集。
对于更改,我们依次由重链向上跳,每次对一个重链的
单点修改即可。
如图,假设我们要修改最下面的一个橙色点。粗线是重链,细线是轻边,蓝色箭头表示了我们的访问过程,橙色元素是需要在线段树上单点修改的元素。
代码
注意一下更新
不好直接覆盖,而是给其一个增量。另外本题中,观察转移矩阵乘法的方向,DP 的方向是由叶子到根,对应的转移矩阵是从右至左乘的,但是在线段树上叶子到根 DFN 递减,因此线段树合并儿子结点和查询是 lch
乘 rch
。写动态 DP 一定要注意乘法的方向,因为矩阵乘法没有交换律!例如 [CodeForces 750E] New Year and Old Subsequence 这道题就是 rch
乘 lch
!
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
const int INF = 0x3f3f3f3f;
const int MAXN = 100000;
const int MAXP = 2;
int N, Q;
int A[MAXN + 5];
std::vector<int> G[MAXN + 5];
struct Matrix {
int n, m;
int Mat[MAXP + 1][MAXP + 1];
Matrix(int _n = 2, int _m = 2) {
n = _n, m = _n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
Mat[i][j] = -INF;
}
int* operator [] (const int &i) {
return Mat[i];
}
Matrix operator * (Matrix other) {
Matrix ret(n, other.m);
for (int i = 1; i <= ret.n; i++)
for (int j = 1; j <= ret.m; j++)
for (int k = 1; k <= m; k++)
ret[i][j] = std::max(ret[i][j], Mat[i][k] + other[k][j]);
return ret;
}
void Debug() {
printf("n = %d, m = %d\n", n, m);
for (int i = 1; i <= n; i++, puts(""))
for (int j = 1; j <= m; j++)
printf("%d ", Mat[i][j]);
}
}H[MAXN + 5];
int Dep[MAXN + 5];
int Top[MAXN + 5], End[MAXN + 5];
int Dfn[MAXN + 5], Tid[MAXN + 5], DfnCnt;
int Size[MAXN + 5], Son[MAXN + 5], Fa[MAXN + 5];
struct SegmentTree {
#define lch (u << 1)
#define rch (u << 1 | 1)
Matrix Trans[(MAXN << 2) + 5];
void PushUp(int u) {
Trans[u] = Trans[lch] * Trans[rch];
}
void Build(int u, int lft, int rgt) {
if (lft == rgt) {
Trans[u] = H[Tid[lft]];
return;
}
int mid = (lft + rgt) >> 1;
Build(lch, lft, mid);
Build(rch, mid + 1, rgt);
PushUp(u);
}
void Modify(int u, int lft, int rgt, int pos) {
if (lft == rgt) {
Trans[u] = H[Tid[pos]];
return;
}
int mid = (lft + rgt) >> 1;
if (pos <= mid) Modify(lch, lft, mid, pos);
else Modify(rch, mid + 1, rgt, pos);
PushUp(u);
}
Matrix Query(int u, int lft, int rgt, int l, int r) {
if (l <= lft && rgt <= r)
return Trans[u];
int mid = (lft + rgt) >> 1;
if (l > mid) return Query(rch, mid + 1, rgt, l, r);
if (r <= mid) return Query(lch, lft, mid, l, r);
return Query(lch, lft, mid, l, r) * Query(rch, mid + 1, rgt, l, r);
}
}T;
void Dfs1(int u, int fa) {
int Max = 0;
Fa[u] = fa; Size[u] = 1;
for (int i = 0; i < int(G[u].size()); i++) {
int v = G[u][i];
if (v != fa) {
Dfs1(v, u);
Size[u] += Size[v];
if (Size[v] > Max)
Max = Size[Son[u] = v];
}
}
}
int Dp[MAXN + 5][2];
#define F0(i) Dp[i][0]
#define F1(i) Dp[i][1]
#define G0(i) H[i][1][1]
#define G1(i) H[i][2][1]
void Dfs2(int u, int top) {
Top[Tid[Dfn[u] = ++DfnCnt] = u] = top, End[Top[u]] = Dfn[u];
F0(u) = G0(u) = 0, F1(u) = G1(u) = A[u];
if (Son[u]) {
Dfs2(Son[u], top);
F1(u) += F0(Son[u]);
F0(u) += std::max(F0(Son[u]), F1(Son[u]));
}
for (int i = 0; i < int(G[u].size()); i++) {
int v = G[u][i];
if (v != Fa[u] && v != Son[u]) {
Dfs2(v, v);
G1(u) += F0(v), G0(u) += std::max(F0(v), F1(v));
F1(u) += F0(v), F0(u) += std::max(F0(v), F1(v));
}
}
H[u][1][2] = H[u][1][1];
}
void Modify(int u, int x) {
H[u][2][1] += x - A[u], A[u] = x;
while (u) {
Matrix x = T.Query(1, 1, N, Dfn[Top[u]], End[Top[u]]);
T.Modify(1, 1, N, Dfn[u]);
Matrix y = T.Query(1, 1, N, Dfn[Top[u]], End[Top[u]]);
u = Fa[Top[u]];
G0(u) += std::max(y[1][1], y[2][1]) - std::max(x[1][1], x[2][1]);
H[u][1][2] = H[u][1][1];
G1(u) += y[1][1] - x[1][1];
}
}
int main() {
scanf("%d%d", &N, &Q);
for (int i = 1; i <= N; i++)
scanf("%d", &A[i]);
for (int i = 1; i < N; i++) {
int u, v; scanf("%d%d", &u, &v);
G[u].push_back(v); G[v].push_back(u);
}
Dfs1(1, 0);
Dfs2(1, 1);
T.Build(1, 1, N);
while (Q--) {
int u, x;
scanf("%d%d", &u, &x);
Modify(u, x);
Matrix Ans = T.Query(1, 1, N, 1, End[1]);
printf("%d\n", std::max(Ans[1][1], Ans[2][1]));
}
return 0;
}