zoukankan      html  css  js  c++  java
  • 【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 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$左右。

    #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;
    }
    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
    ", 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;
    }
    View Code
  • 相关阅读:
    教你在mac上配置adb环境变量
    Android Mediaplayer 调用release()的时候ANR
    Android ANR
    安卓android WebView Memory Leak WebView内存泄漏
    android 小结
    struts提供文件上传功能
    struts自定义拦截器
    struts拦截器的配置和使用
    Struts框架属性驱动
    struts配置文件
  • 原文地址:https://www.cnblogs.com/Dance-Of-Faith/p/10015440.html
Copyright © 2011-2022 走看看