【NOIP 2018】保卫王国(动态dp / 倍增)

题目链接

 这个$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 \neq s(x)} f_{v, 1} \qquad g_{x, 1} = a_{x} + \sum_{v, v \neq 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$上的矩阵:

$$\begin{bmatrix}\infty & g_{x,0} \\g_{x,1} & g_{x, 1} \end{bmatrix}\begin{pmatrix} f_{s_{x},0} \\f_{s_x,1}\end{pmatrix}=\begin{pmatrix}f_{x,0} \\f_{x,1}\end{pmatrix}$$

特别的,我们有单位矩阵: $\begin{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$左右。

#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\n", ans < BINF / 7? ans : -1);
    Modify(x, lx);
    Modify(y, ly);
  }
  return 0;
}
View Code

 

法二:倍增$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$的亲儿子上,最后再枚举状态求一下就好了。

#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\n", 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\n", min(t0, t1) > INF / 100? -1 : min(t0, t1));
  }
  return 0;
}
View Code

 

posted @ 2018-11-25 13:46  Dance_Of_Faith  阅读(554)  评论(0编辑  收藏  举报