这个$dark$题,嗯,不想说了。
法一:动态$dp$
虽然早有听闻动态$dp$,但到最近才学,如果你了解动态$dp$,那就能很轻松做出这道题了。故利用这题在这里科普一下动态$dp$的具体内容。
我们先不考虑点上的强制选不选的限制,这是一个最小权边覆盖问题,大家肯定都会这道题的$O(nm)$的做法,这是一个很经典的树形$dp$。具体来讲就是一下两个转移:
$$f_{x, 0} = sum_{v} f_{v, 1} qquad f_{x, 1} = a_{x} + sum_{v} min(f_{v, 0} , f_{v, 1})$$
其中$f_{x, 0/1}$表示$x$这个点选/不选时$x$这个子树下的最少花费,$v$是$x$的亲儿子。
问题在树上,我们通常考虑树链剖分,并用$s(x)$表示$x$的重儿子。同时我们引出有关$x$新函数$g$如下:
$$g_{x, 0} = sum_{v, v eq s(x)} f_{v, 1} qquad g_{x, 1} = a_{x} + sum_{v, v eq s(x)} min(f_{v, 0}, f_{v, 1})$$
于是有关$f$的转移可以改写成:
$$f_{x, 0} = f_{s(x), 1} + g_{x, 0} qquad f_{x, 1} = min(f_{s(x), 0}, f_{s(x), 1}) + g_{x, 1}$$
这么做的目的在于把重儿子单独分离开来,这样在$g$中是不包含重儿子的信息的。我们过一会就能看到它的用处。
上述改写后的是一个有加法和取$min$的一个转移,我们把矩阵乘法中的乘法变成加法,把加法变成取$min$,那我们可以用一个线性变换来描述它,我们称它为$x$上的矩阵:
$$egin{bmatrix}infty & g_{x,0} \g_{x,1} & g_{x, 1} end{bmatrix}egin{pmatrix} f_{s_{x},0} \f_{s_x,1}end{pmatrix}=egin{pmatrix}f_{x,0} \f_{x,1}end{pmatrix}$$
特别的,我们有单位矩阵: $egin{bmatrix}0 & infty \infty & 0 end{bmatrix}$。
这么做的好处在于原本一个自下而上的$dp$,可以被转变为矩阵乘法,一个点$x$的$f$可以由$x$点到它所在的重链的链尾上所有矩阵的乘积表示。我们可以用线段树维护链上矩阵的乘积,就能快速算得我们想要的$dp$值。
我们考虑如果要修改某一个点$x$的点权,我们如何维护矩阵的变化。首先我们都知道只有$x$的祖先的$dp$值可能会变化,并且如果$x$所在的儿子是某个祖先$y$的重儿子,那$g_y$就不会变化。由于我们的矩阵中只有关于$g$的信息,故$y$的矩阵也不会变化。所以事实上会发生变化的矩阵只有祖先链上的$O(logn)$条轻边的父亲的矩阵。我们可以自下而上每次暴力跳到那几条轻边,先在线段树上查得轻边儿子的$f$,然后把它父亲的$g$更新,修改矩阵。那么我们就能$O(log^2n)$维护点权修改了。注意这里我们每次会重新算链头的$f$值,所以任意时刻链头的$f$值都是对的,而非链头的点的$f$值是不一定准确的。
这就是动态$dp$的大致内容,我们可以整理一下思路。首先我们把$dp$的过程用线性变换替代,于是用矩阵的乘积表示某点的$dp$值。对于每次修改,我们暴力跳轻边来更新矩阵。
现在我们已经知道如何在支持修改点权的情况下,动态维护一棵子树下的最小权边覆盖问题。回过头来看这道题就显得非常容易了,题中的限制条件就可以通过把点权设成$-inf/inf$来实现。
这里我把矩阵乘法手动展开了,大概能快$400ms$左右。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <algorithm> using namespace std; typedef long long LL; const int N = 100005; const LL INF = (LL)1e17; const LL BINF = INF / 10; int n, nq; int fa[N], tp[N], so[N], si[N], df[N], dw[N], li[N]; LL val[N], g[N][2], f[N][2]; struct Mat { LL v[2][2]; Mat(LL a = 0, LL b = 0) { v[0][0] = INF, v[0][1] = a; v[1][0] = v[1][1] = b; } friend Mat operator * (Mat &a, Mat &b) { static Mat c; c.v[0][0] = min(a.v[0][0] + b.v[0][0], a.v[0][1] + b.v[1][0]); c.v[0][1] = min(a.v[0][0] + b.v[0][1], a.v[0][1] + b.v[1][1]); c.v[1][0] = min(a.v[1][0] + b.v[0][0], a.v[1][1] + b.v[1][0]); c.v[1][1] = min(a.v[1][0] + b.v[0][1], a.v[1][1] + b.v[1][1]); return c; } } I; int yu, la[N], to[N << 1], pr[N << 1]; inline void Ade(int a, int b) { to[++yu] = b, pr[yu] = la[a], la[a] = yu; } void Dfs0(int x, int fat) { si[x] = 1, f[x][1] = val[x]; for (int i = la[x]; i; i = pr[i]) { if (to[i] == fat) continue; fa[to[i]] = x; Dfs0(to[i], x); si[x] += si[to[i]]; if (si[to[i]] > si[so[x]]) so[x] = to[i]; f[x][0] += f[to[i]][1]; f[x][1] += min(f[to[i]][0], f[to[i]][1]); } } void Dfs1(int x, int gr) { li[df[x] = ++*li] = x; tp[x] = gr, dw[x] = x, g[x][1] = val[x]; if (so[x]) Dfs1(so[x], gr), dw[x] = dw[so[x]]; for (int i = la[x]; i; i = pr[i]) if (to[i] != fa[x] && to[i] != so[x]) { Dfs1(to[i], to[i]); g[x][0] += f[to[i]][1]; g[x][1] += min(f[to[i]][0], f[to[i]][1]); } } namespace SE { int B; Mat t[N << 2 | 1]; void Bu(int n) { for (B = 1; B < n + 2; B <<= 1); for (int i = 1; i <= n; ++i) t[B + i] = Mat(g[li[i]][0], g[li[i]][1]); for (int i = B - 1; i; --i) t[i] = t[i << 1] * t[i << 1 | 1]; } void Mo(int x) { t[x + B] = Mat(g[li[x]][0], g[li[x]][1]); for ((x += B) >>= 1; x; x >>= 1) t[x] = t[x << 1] * t[x << 1 | 1]; } Mat Qr(int l, int r) { Mat r0 = I, r1 = I; for (l += B - 1, r += B + 1; l ^ r ^ 1; l >>= 1, r >>= 1) { if (~l & 1) r0 = r0 * t[l ^ 1]; if (r & 1) r1 = t[r ^ 1] * r1; } return r0 * r1; } } void Modify(int x, LL _v) { g[x][1] += _v - val[x], val[x] = _v; for (; x; x = fa[x]) { SE::Mo(df[x]), x = tp[x]; Mat tf = SE::Qr(df[x], df[dw[x]]); g[fa[x]][0] -= f[x][1]; g[fa[x]][1] -= min(f[x][0], f[x][1]); f[x][0] = tf.v[0][1], f[x][1] = tf.v[1][1]; g[fa[x]][0] += f[x][1]; g[fa[x]][1] += min(f[x][0], f[x][1]); } } int main() { I.v[0][1] = I.v[1][0] = INF; I.v[0][0] = I.v[1][1] = 0; scanf("%d%d%*s", &n, &nq); for (int i = 1; i <= n; ++i) scanf("%lld", &val[i]); for (int i = 1, x, y; i < n; ++i) { scanf("%d%d", &x, &y); Ade(x, y), Ade(y, x); } Dfs0(1, 0), Dfs1(1, 1); SE::Bu(n); for (int a, b, x, y; nq--; ) { scanf("%d%d%d%d", &x, &a, &y, &b); LL lx = val[x], ly = val[y]; Modify(x, a? lx - BINF : BINF); Modify(y, b? ly - BINF : BINF); LL ans = min(f[1][0], f[1][1]); ans += (a? BINF : 0) + (b? BINF : 0); printf("%lld ", ans < BINF / 7? ans : -1); Modify(x, lx); Modify(y, ly); } return 0; }
法二:倍增$dp$
由于这道题并没有涉及点权修改,我们可以用倍增在实现$dp$的快速转移。设$f_{x, 0/1}$表示$x$点的子树下,$x$点选与不选时的最小花费,设$g_{x, 0/1}$表示除了$x$子树外的树的其他部分,在$x$点选与不选时的最小花费。显然这个可以$O(n)$树形$dp$出来。
在令$h_{x, i, 0/1, 0/1}$表示$x$的$2^i$级祖先$y$的子树下,并用$0/1$表示两者的状态时的最小花费,这个可以$O(nlogn)$求出来,和普通的倍增一样,合并时枚举几个点的状态即可。
在求每个询问的答案时,如果两个点$x,y$,其中$y$是$x$的祖先,那么可以直接倍增上去;否则$x,y$都倍增到$lca$的亲儿子上,最后再枚举状态求一下就好了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; const int LG = 17; const int N = 100005; const LL INF = (LL)1e17; int n, nq; int val[N], dep[N], gr[LG][N]; LL f[2][N], g[2][N]; int yu, la[N], pr[N << 1], to[N << 1]; inline void Ade(int a, int b) { to[++yu] = b, pr[yu] = la[a], la[a] = yu; } struct Mat { LL v[2][2]; Mat(LL x = INF, LL y = INF) { v[0][0] = x, v[1][1] = y; v[0][1] = v[1][0] = INF; } friend Mat Mul(Mat a, Mat b, int y) { static Mat c; // a = x -> y, b = y -> z for (int i = 0; i < 2; ++i) for (int k = 0; k < 2; ++k) c.v[i][k] = min(b.v[0][k] - f[0][y] + a.v[i][0], b.v[1][k] - f[1][y] + a.v[i][1]); // inf return c; } } h[LG][N]; void Dfs0(int x, int fat) { f[1][x] = val[x]; for (int i = la[x]; i; i = pr[i]) { int v = to[i]; if (v != fat) { dep[v] = dep[x] + 1; Dfs0(v, x); gr[0][v] = x; f[0][x] += f[1][v]; f[1][x] += min(f[0][v], f[1][v]); } } } void Dfs1(int x, int fat) { for (int i = la[x]; i; i = pr[i]) { int v = to[i]; Mat &h0 = h[0][v]; if (v != fat) { LL t0 = g[0][x] + f[0][x] - f[1][v]; LL t1 = g[1][x] + f[1][x] - min(f[0][v], f[1][v]); g[0][v] = t1; g[1][v] = min(t0, t1); Dfs1(v, x); h0.v[0][0] = INF; h0.v[1][0] = f[0][x]; h0.v[0][1] = f[1][x] - min(f[0][v], f[1][v]) + f[0][v]; h0.v[1][1] = f[1][x] - min(f[0][v], f[1][v]) + f[1][v]; } } } int main() { scanf("%d%d%*s", &n, &nq); for (int i = 1; i <= n; ++i) scanf("%d", &val[i]); for (int i = 1, x, y; i < n; ++i) { scanf("%d%d", &x, &y); Ade(x, y), Ade(y, x); } dep[1] = 1, Dfs0(1, 0), Dfs1(1, 0); for (int r = 1; r < LG; ++r) { for (int i = 1; i <= n; ++i) { int y = gr[r - 1][i]; gr[r][i] = gr[r - 1][y]; h[r][i] = Mul(h[r - 1][i], h[r - 1][y], y); } } for (int a, b, x, y; nq--; ) { scanf("%d%d%d%d", &x, &a, &y, &b); if (dep[x] > dep[y]) { swap(x, y), swap(a, b); } Mat ay(f[0][y], f[1][y]); int v = y, fy = 1; for (int i = LG - 1; ~i; --i) { if (dep[gr[i][v]] >= dep[x]) { if (fy) ay = h[i][v], fy = 0; else ay = Mul(ay, h[i][v], v); v = gr[i][v]; } } if (x == v) { printf("%lld ", ay.v[b][a] + g[a][x] > INF / 100? -1 : ay.v[b][a] + g[a][x]); continue; } Mat ax(f[0][x], f[1][x]); int u = x, fx = 1; for (int i = LG - 1; ~i; --i) { if (gr[i][u] != gr[i][v]) { if (fx) ax = h[i][u], fx = 0; else ax = Mul(ax, h[i][u], u); if (fy) ay = h[i][v], fy = 0; else ay = Mul(ay, h[i][v], v); u = gr[i][u], v = gr[i][v]; } } int lc = gr[0][u]; LL t0 = g[0][lc] + f[0][lc] - f[1][u] + ax.v[a][1] - f[1][v] + ay.v[b][1]; LL t1 = g[1][lc] + f[1][lc] - min(f[0][u], f[1][u]) + min(ax.v[a][0], ax.v[a][1]) - min(f[0][v], f[1][v]) + min(ay.v[b][0], ay.v[b][1]); printf("%lld ", min(t0, t1) > INF / 100? -1 : min(t0, t1)); } return 0; }