P5024 保卫王国笔记

神题,神题啊!!(战术后仰)

题意

给定一棵 n 个节点的树,点有点权。有 m 个询问,每次询问规定某两个点选或不选,求最小点覆盖。

题解

概念

做题的第一步是:看懂题意。

  • 点覆盖:在树上选择某些点,使得对于树上的每一条边,都满足两端点至少有一点被选。

  • 最小点覆盖:即选择的点点权和最小的点覆盖。

  • 独立集:在树上选择某些点,使得对于树上的每一条边,都满足两端点没有同时被选。

  • 最大独立集:即选择的点点权最大的独立集。

不难看出,一棵树上的点覆盖和独立集是一一对应的,一个点覆盖的补集就是一个独立集。所以,最大独立集是最小点覆盖的补集。(虽然这些东西和题意无关,但还是可以扯一下)

思路

先考虑没有询问时,如何求得最小点覆盖?答案是显然的,直接树形 dp 即可,设 fx,0/1 表示 x 为根的子树中,x 选或不选的最小点覆盖权值和,则有:

fx,0=vx.sonfv,1fx,1=vx.sonmin{fv,0,fv,1}+px

接着可以发现使用这种 dp,可以用动态 dp 来解决此题。但是这里要讲一种常数较小,代码较短的思想。

由于没有修改,所以不用动态 dp,直接使用倍增即可。考虑倍增所使用的状态 hu,i,0/1,0/1 (设 pu 的第 2i 级祖先),表示从 p 的子树中抠掉 u 这棵子树,第一个 0/1 表示 u 选还是不选(关于这一维的意义:这里 u 虽然已经从 p 中扣掉了,但是在最后回答询问答案时会用到,所以还是要记录一下),第二个表示 p 选还是不选。

那么有递推式:

hu,i,a,b=mink{0,1}hu,i1,a,k+hfau,i1,i1,k,ba,b{0,1}

然后再考虑新添一个辅助数组 gx,0/1,表示整颗树扣掉了 x 为根的子树,且 x 选或不选的答案。于是有(设 px 父亲):

gx,0=gp,1+fp,1min{fx,0,fx,1}gx,1=min{gx,0,gp,0+fp,0fx,1}

于是在一次询问中,我们可以根据这几个数组求得答案了,具体如下:

这里给了 a,b,x,y,可以求出 a,bLCA。我们可以倍增到 LCA 的儿子,求出 ab(如图所示)。假设跳到 a,b 的倍增数组分别是 A0/1,0/1,B0/1,0/1,答案就是以下两种情况的 min

{fa,x+fb,y+gLCA,0+Aa,1+Bb,1fa,x+fb,y+gLCA,1+min{Aa,0,Aa,1}+min{Bb,0,Bb,1}

不难看出两种情况的区别就是 LCA 选还是不选。注意这里还要加上 LCA 的儿子中除了 a,b 的其它儿子的贡献(图中未画出)

于是这道题就思路就完了。

代码

用时 ****(数据删除)写的:

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
#define int i64
const int N = 1E5 + 5, M = 20, INF = 1E15;
int n, m, p[N]; vector <int> G[N];
int yf[N][M + 1], dep[N];
i64 f[N][2], h[N][M + 1][2][2], g[N][2];
void dfs(int x, int fa) {
  f[x][0] = 0; f[x][1] = p[x];
  for (auto v : G[x]) {
    if (v == fa) continue;
    yf[v][0] = x; dep[v] = dep[x] + 1;
    for (int i = 1; i <= M; ++i) 
      yf[v][i] = yf[yf[v][i - 1]][i - 1];
    dfs(v, x);
    f[x][0] += f[v][1];
    f[x][1] += min(f[v][0], f[v][1]);
  }
}
void dfs1(int x, int fa) {
  for (auto v : G[x]) {
    if (v == fa) continue;
    h[v][0][0][0] = INF;
    h[v][0][0][1] = f[x][1] - min(f[v][0], f[v][1]);
    h[v][0][1][0] = f[x][0] - f[v][1];
    h[v][0][1][1] = f[x][1] - min(f[v][0], f[v][1]);
    for (int i = 1; i <= M; ++i) {
    for (int a : {0, 1}) for (int b : {0, 1}) {
        h[v][i][a][b] = INF;
        for (int k : {0, 1})
          h[v][i][a][b] = min(h[v][i][a][b], 
          h[v][i - 1][a][k] + h[yf[v][i - 1]][i - 1][k][b]);
      }
    }
    g[v][0] = g[x][1] + f[x][1] - min(f[v][0], f[v][1]);
    g[v][1] = min(g[v][0], g[x][0] + f[x][0] - f[v][1]);
    dfs1(v, x);
  }
}
int solve(int a, int x, int b, int y) {
  if (dep[a] < dep[b]) swap(a, b), swap(x, y); 
  int A[2] = {INF, INF}, B[2] = {INF, INF};
  A[x] = f[a][x]; B[y] = f[b][y];
  for (int i = M; ~i; --i) {
    if (dep[a] - (1 << i) >= dep[b]) {
      int pA[] = {A[0], A[1]};
      A[0] = min(pA[1] + h[a][i][1][0], pA[0] + h[a][i][0][0]);
      A[1] = min(pA[1] + h[a][i][1][1], pA[0] + h[a][i][0][1]);
      a = yf[a][i];   
    }
  }
  if (a == b) return g[b][y] + A[y];
  else {
    for (int i = M; ~i; --i) {
      if (yf[a][i] != yf[b][i]) {
        int pA[] = {A[0], A[1]}, pB[] = {B[0], B[1]};
        A[0] = min(pA[1] + h[a][i][1][0], pA[0] + h[a][i][0][0]);
        A[1] = min(pA[1] + h[a][i][1][1], pA[0] + h[a][i][0][1]); 
        B[0] = min(pB[1] + h[b][i][1][0], pB[0] + h[b][i][0][0]);
        B[1] = min(pB[1] + h[b][i][1][1], pB[0] + h[b][i][0][1]);
        a = yf[a][i]; b = yf[b][i];
      }
    }
    int lca = yf[a][0];
    int ans0 = f[lca][0] - f[a][1] - f[b][1] + A[1] + B[1] + g[lca][0];
    int ans1 = f[lca][1] - min(f[a][0], f[a][1]) - min(f[b][0], f[b][1]) + min(A[1], A[0]) + min(B[1], B[0]) + g[lca][1];
    return min(ans0, ans1);
  }
}
signed main(void) {
  ios :: sync_with_stdio(false);
  cin.tie(nullptr); cout.tie(nullptr);
  cin >> n >> m; string typp; cin >> typp; typp = "";
  for (int i = 1; i <= n; ++i) cin >> p[i];
  for (int i = 1; i < n; ++i) {
    int u, v; cin >> u >> v;
    G[u].emplace_back(v);
    G[v].emplace_back(u);
  } dep[1] = 1; for (int i = 0; i <= M; ++i) yf[1][i] = 1;
  dfs(1, 0); dfs1(1, 0);
  map <pair <int, int>, bool> vis;
  function <void(int, int)> prep = [&](int x, int fa) {
    for (auto v : G[x]) {
      if (v == fa) continue;
      vis[make_pair(v, x)] = vis[make_pair(x, v)] = 1;
      prep(v, x);
    }
  } ; prep(1, 0);
  for (int i = 1; i <= m; ++i) {
    int a, x, b, y; cin >> a >> x >> b >> y;
    if (!x && !y && vis.count(make_pair(a, b))) {
      cout << "-1\n";
      continue;
    }
    cout << solve(a, x, b, y) << '\n';
  }
}

作者:CTHOOH

出处:https://www.cnblogs.com/CTHOOH/p/17985878

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   CTHOOH  阅读(44)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示