「SDOI2011」消耗战


知识点: 虚树,DP

原题面:Luogu

在学习如何用虚树优化后缀树时学会了虚树

简述

给定一棵 \(n\) 个节点的树,边有边权。
给定 \(m\) 次询问,每次给定 \(k\) 个关键点,要求切除一些边,使得 \(k\) 个关键点与编号为 \(1\) 的点不连通。
最小化切除的边的权值之和。
\(2\le n\le 2.5\times 10^5\)\(1\le m\le 5\times 10^5\)\(\sum k \le 5\times 10^5\)\(1\le k\le n\),边权值 \(w\le 10^5\)
2S,512MB。

分析

首先想到一个简单的 DP。对于单次查询,设 \(f_u\) 为令以 \(u\) 为根的子树中的所有关键点 与 \(u\) 不连通的最小代价。
转移时枚举 \(u\) 的子节点,有状态转移方程:

\[f_{u} = \sum_{v\in son_u} \begin{cases} w(u,v) &(v\text{ is a key node})\\ \min\{w(u,v), f_v\} &\text{otherwise} \end{cases}\]

单次查询复杂度 \(O(n)\),总复杂度 \(O(nm)\),无法通过本题。

发现关键点集较小,不含任何关键点的子树显然无用,考虑建立虚树。
发现使得一个关键点 \(u\) 与根不相连的最小代价为根到关键点路径上最短的边长,设其为 \(\operatorname{val}_u\),在 dfs 时顺便维护。对于建立的虚树,有新的状态转移方程:

\[f_{u} = \sum_{v\in son'_u} \begin{cases} \operatorname{val}_v &(v\text{ is a key node})\\ \min\{\operatorname{val}_v, f_v\} &\text{otherwise} \end{cases}\]

总复杂度 \(O(\sum k)\) 级别,可以通过本题。
对于本题,还可以删除以关键点作为祖先的关键点 进行进一步的优化。正确性显然,因为一定要使得其祖先与根不相连。

代码

//知识点:虚树
/*
By:Luckyblock
*/
#include 
#include 
#include 
#include 
#include 
#define LL long long
const int kMaxn = 2e5 + 5e4 + 10;
const int kMaxm = 5e5 + 10;
const LL kInf = 1e15 + 2077;
//=============================================================
int n, m, edge_num, head[kMaxn], v[kMaxm << 1], w[kMaxm << 1], ne[kMaxm << 1];
std :: vector  newv[kMaxn];
int top, node[kMaxn], st[kMaxn];
bool tag[kMaxn];
LL minw[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMin(LL &fir, LL sec) {
  if (sec < fir) fir = sec;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ edge_num] = v_, w[edge_num] = w_;
  ne[edge_num] = head[u_], head[u_] = edge_num;
}
namespace TCC { //TreeChainCut
  int fa[kMaxn], dep[kMaxn], size[kMaxn], son[kMaxn], top[kMaxn];
  int dfn_num, dfn[kMaxn];
  void Dfs1(int u_, int fa_) {
    fa[u_] = fa_;
    size[u_] = 1;
    dep[u_] = dep[fa_] + 1;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (v_ == fa_) continue;
      minw[v_] = std :: min(minw[u_], (LL) w_);
      Dfs1(v_, u_);
      size[u_] += size[v_];
      if (size[v_] > size[son[u_]]) son[u_] = v_;
    }
  }
  void Dfs2(int u_, int top_) {
    top[u_] = top_;
    dfn[u_] = ++ dfn_num;
    if (son[u_]) Dfs2(son[u_], top_);
    for (int i = head[u_]; i; i = ne[i]) {
      if (v[i] == son[u_] || v[i] == fa[u_]) continue;
      Dfs2(v[i], v[i]);
    }
  }
  int Lca(int u_, int v_) {
    for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
      if (dep[top[u_]] < dep[top[v_]]) std :: swap(u_, v_);
    }
    return (dep[u_] < dep[v_]) ? u_ : v_;
  }
}
bool CMP(int fir, int sec) {
  return TCC::dfn[fir] < TCC::dfn[sec];
}
LL Dfs(int u_) {
  LL sum = 0;
  for (int i = 0, size = newv[u_].size(); i < size; ++ i) {
    sum += Dfs(newv[u_][i]);
  }
  newv[u_].clear();
  if (tag[u_]) {
    tag[u_] = false;
    return minw[u_];
  }
  return std::min(minw[u_], sum);
}
#define dep (TCC::dep)
void Push(int u_) {
  int lca = TCC::Lca(u_, st[top]);
    for (; dep[st[top - 1]] > dep[lca]; -- top) {
      newv[st[top - 1]].push_back(st[top]);
    }
    if (lca != st[top]) {
      newv[lca].push_back(st[top]); -- top;
      if (lca != st[top]) st[++ top] = lca;
    }
    st[++ top] = u_;
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    AddEdge(u_, v_, w_), AddEdge(v_, u_, w_);
  }
  minw[1] = kInf;
  TCC::Dfs1(1, 0), TCC::Dfs2(1, 1);
  
  m = read();
  for (int i = 1; i <= m; ++ i) {
    int k = read();
    for (int j = 1; j <= k; ++ j) {
      node[j] = read();
      tag[node[j]] = true;
    }
    std :: sort(node + 1, node + k + 1, CMP);

    st[top = 0] = 1;
    for (int j = 1; j <= k; ++ j) Push(node[j]);
    for (; top; -- top) newv[st[top - 1]].push_back(st[top]);
    printf("%lld\n", Dfs(1));
  }
  return 0; 
}