题目
分析
首先写出朴素的 DP 式: f [ u ] [ 0 / 1 ] f[u][0/1] f[u][0/1] 表示不选 / / / 选 u u u 点, u u u 的子树内的最大权独立集的权值和。设 v v v是 u u u 的儿子(下同),则 f [ u ] [ 0 ] = ∑ v max { f [ v ] [ 0 ] , f [ v ] [ 1 ] } f [ u ] [ 1 ] = ∑ v f [ v ] [ 0 ] + a u \begin{aligned}f[u][0] &= \sum_{v} \max\{f[v][0], f[v][1]\} \\ f[u][1] &= \sum_{v} f[v][0] + a_u\end{aligned} f[u][0]f[u][1]=v∑max{f[v][0],f[v][1]}=v∑f[v][0]+au 用树链剖分将树转化成一个线性结构维护动态 DP 的转移矩阵。具体来说,需要将转移矩阵设计成可以由重链传递的信息,因此设 h h h 是 u u u 的重儿子,且 g [ u ] [ 0 ] = ∑ v ≠ h max { f [ v ] [ 0 ] , f [ v ] [ 1 ] } g [ u ] [ 1 ] = ∑ v ≠ h f [ v ] [ 0 ] + a u \begin{aligned} g[u][0] &= \sum_{v \neq h} \max\{f[v][0], f[v][1]\} \\ g[u][1] &= \sum_{v \neq h} f[v][0] + a_u\end{aligned} g[u][0]g[u][1]=v=h∑max{f[v][0],f[v][1]}=v=h∑f[v][0]+au 于是 f [ u ] [ 0 ] = g [ u ] [ 0 ] + max { f [ h ] [ 0 ] , f [ h ] [ 1 ] } f [ u ] [ 1 ] = g [ u ] [ 1 ] + f [ h ] [ 0 ] \begin{aligned} f[u][0] &= g[u][0] + \max\{f[h][0], f[h][1]\} \\ f[u][1] &= g[u][1] + f[h][0] \end{aligned} f[u][0]f[u][1]=g[u][0]+max{f[h][0],f[h][1]}=g[u][1]+f[h][0] 转化一下 f [ u ] [ 0 ] = max { g [ u ] [ 0 ] + f [ h ] [ 0 ] , g [ u ] [ 0 ] + f [ h ] [ 1 ] } f [ u ] [ 1 ] = max { g [ u ] [ 1 ] + f [ h ] [ 0 ] , − ∞ } \begin{aligned} f[u][0] &= \max\{g[u][0] + f[h][0], g[u][0] + f[h][1]\} \\ f[u][1] &= \max\{g[u][1] + f[h][0], -\infty\}\end{aligned} f[u][0]f[u][1]=max{g[u][0]+f[h][0],g[u][0]+f[h][1]}=max{g[u][1]+f[h][0],−∞} 就变成了喜闻乐见的矩阵“乘法”形式。将常规乘法重新定义 + → max , × → + + \to \max, \times \to + +→max,×→+,于是有 ( g [ u ] [ 0 ] g [ u ] [ 0 ] g [ u ] [ 1 ] − ∞ ) × ( f [ h ] [ 0 ] f [ h ] [ 1 ] ) = ( f [ u ] [ 0 ] f [ u ] [ 1 ] ) \begin{pmatrix} g[u][0] & g[u][0] \\ g[u][1] & -\infty \end{pmatrix} \times \begin{pmatrix} f[h][0] \\ f[h][1] \end{pmatrix} = \begin{pmatrix} f[u][0] \\ f[u][1] \end{pmatrix} (g[u][0]g[u][1]g[u][0]−∞)×(f[h][0]f[h][1])=(f[u][0]f[u][1]) 于是我们可以在重链上维护 2 × 2 2 \times 2 2×2 的转移矩阵的乘积,事实上 g g g 数组将将重链周围的轻儿子的信息整合到了该重链上,这样一层一层传递,最终只需要计算以 1 1 1 为顶的重链的转移矩阵乘积,就得到了整个树的最大权独立集。
对于更改,我们依次由重链向上跳,每次对一个重链的
g
g
g 单点修改即可。
如图,假设我们要修改最下面的一个橙色点。粗线是重链,细线是轻边,蓝色箭头表示了我们的访问过程,橙色元素是需要在线段树上单点修改的元素。
代码
注意一下更新
g
g
g 不好直接覆盖,而是给其一个增量。另外本题中,观察转移矩阵乘法的方向,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;
}