消耗战

题目地址:

https://ac.nowcoder.com/acm/problem/212521

基本思路:

先补充一下数据范围:

,,

我们发现如果没有次的查询单单是询问一次,那么设为切断这颗子树下所有点的最小代价,号点的路径中最小的边权,我们可以很容易的得到一个树形的转移方程:

但是我们肯定不能对每次的查询暴力跑,我们观察到,似乎可以从这里入手,因此我们引入虚树的概念。
虚树,实际上就是对于每次查询的点,我们只针对这些有用的点建树,而忽略那些用不到的点,对于虚树的构建我们可以参考下面的步骤:
首先我们要将每次查询的点,根据序进行排序;
然后我们维护一个栈,栈底到栈顶的元素表示虚树中从上到下的一条链,
在这里我们先固定放进根节点,对于每次插入的节点,

当栈中只有一个元素的时候我们直接将插入栈,

否则我们令,

,那么意味着在栈顶节点的子树中,那么直接将压入栈中,

如果,那么说明分属的两棵不同的子树,而且所在的子树中已经构建完成了,这时候我们弹栈并且在弹栈过程中连边,直到 时停止,这时再判断是否等于:

若不等,先从连边,然后弹出栈顶,压入,再压入,

否则直接压入
具体过程大家可以根据代码运行过程,画图理解,我不太会用绘图软件,这里就不引入图片了。

这样我们对于每次查询都构建一棵虚树,然后再在虚树上跑前面的树形过程,那么就能在规定时间里得到答案,虚树的复杂度只与有关为

参考代码如下,代码中使用树剖求

参考代码:

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define SZ(x) ((int)(x).size())
#define all(x) (x).begin(), (x).end()
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define per(i, l, r) for (int i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define debug(x) cerr << #x << " = " << x << '\n';
#define pll pair <ll, ll>
#define fir first
#define sec second
#define INF 0x3f3f3f3f

namespace IO {
const int SIZE = 1 << 20;
char buf[SIZE], *p1 = buf, *p2 = buf;
char obuf[SIZE], *O = obuf;
int getc() {
  return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, SIZE, stdin), p1 == p2) ? EOF : *p1++;
}
int read() {
  int x = 0, f = 0;
  char ch = getc();
  while (!isdigit(ch)) f |= ch == '-', ch = getc();
  while (isdigit(ch)) x = 10 * x + ch - '0', ch = getc();
  return f ? -x : x;
}
template<typename T> void print(T x) {
  if (x < 0) *O++ = '-', x = -x;
  if (x > 9) print(x / 10);
  *O++ = char(x % 10 + '0');
}
template<typename T> void print(T x, char opt) {
  print(x), *O++ = opt;
}
void Flush() {
  fwrite(obuf, O - obuf, 1, stdout);
}
}

using namespace IO;
const int maxn = 5e5 + 10;
struct Edge{
    int to,w,next;
}edge[maxn << 1];
int cnt = 0,head[maxn];
void init() {
  cnt = 0;
  mset(head, -1);
}
void add_edge(int u,int v,int w) {
  edge[++cnt].next = head[u];
  edge[cnt].w = w;
  edge[cnt].to = v;
  head[u] = cnt;
}
int sz[maxn],dep[maxn],son[maxn],fa[maxn];
ll mn[maxn];
int dfn[maxn],top[maxn],tot;
void dfs1(int u,int par,int deep){
  dep[u] = deep; fa[u] = par; sz[u] = 1; son[u] = 0;
  for(int i = head[u] ; i != -1 ; i = edge[i].next){
    int to = edge[i].to;
    if(to == par) continue;
    mn[to] = min(mn[u], (ll)edge[i].w);
    dfs1(to,u,deep + 1);
    sz[u] += sz[to];
    if(sz[to] > sz[son[u]]) son[u] = to;
  }
}
void dfs2(int u,int tp) {
  dfn[u] = ++tot;
  top[u] = tp;
  if (son[u]) dfs2(son[u], tp);
  for (int i = head[u]; i != -1; i = edge[i].next) {
    int to = edge[i].to;
    if (to == fa[u] || to == son[u]) continue;
    dfs2(to, to);
  }
}
int LCA(int x,int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]]) swap(x, y);
    x = fa[top[x]];
  }
  if (dep[x] < dep[y]) swap(x, y);
  return y;
}
vector<int> G[maxn];
int stk[maxn],tp;
void add(int x,int y){
  G[x].push_back(y);
}
void insert(int x) {
  if (tp == 1) { stk[++tp] = x; return; }
  int lca = LCA(stk[tp], x);
  if (lca == stk[tp]) return;
  while (tp > 1 && dfn[stk[tp - 1]] >= dfn[lca]) add(stk[tp - 1],stk[tp]),tp--;
  if (lca != stk[tp]) add(lca,stk[tp]),stk[tp] = lca;
  stk[++tp] = x;
}
ll calc(int u){
  if(SZ(G[u]) == 0) return mn[u];
  ll res = 0;
  for(auto to : G[u]) res += calc(to);
  G[u].clear();
  return min(mn[u],res);
}
int n,m,k,a[maxn];
inline bool cmp(int x,int y){
  return dfn[x] < dfn[y];
}
signed main() {
  n = read();
  init();
  rep(i,1,n - 1) {
    int u = read(), v = read(), w = read();
    add_edge(u, v, w);
    add_edge(v, u, w);
  }
  mn[1] = (1ll << 60);
  dfs1(1,0,1);
  dfs2(1,1);
  m = read();
  while (m--) {
    k = read();
    rep(i, 1, k) a[i] = read();
    sort(a + 1, a + 1 + k, cmp);
    stk[tp = 1] = 1;
    rep(i, 1, k) insert(a[i]);
    while (tp > 0) add(stk[tp - 1], stk[tp]), tp--;
    print(calc(1),'\n');
  }
  Flush();
  return 0;
}