思路:
如果是一组询问,很明显可以树形
dp
d
p
,假设以
u
u
为根的子树要断开所有关键点的路径所需要的最小花费, 可以找到状态转移方程:
dp[u]=∑{min_cost(u,son),son是关键点min(min_cost(u,son),dp[son]),son不是关键点
d
p
[
u
]
=
∑
{
m
i
n
_
c
o
s
t
(
u
,
s
o
n
)
,
s
o
n
是
关
键
点
m
i
n
(
m
i
n
_
c
o
s
t
(
u
,
s
o
n
)
,
d
p
[
s
o
n
]
)
,
s
o
n
不
是
关
键
点
但是现在是多组询问, 有个关键的就是
∑ki⩽5e5
∑
k
i
⩽
5
e
5
, 也就是说如果在每组询问中, 能够单独对这些关键点做树形
dp
d
p
也是可以的,这也就是去除无用点依然能保持原树的形态,然后就是构建虚树, 虚树就是在原树去除若干的无用点,只保留一些关键点,那些无用点不影响答案,然后对关键点做树形
dp
d
p
就行了。
关于虚树的构建参考:虚树学习笔记
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e5 + + 5e4 + 100;
const int INF = 1e9 + 10;
using namespace std;
typedef pair<int, int> pa;
int n, m, T, kase = 1;
vector<pa> G[maxn], g[maxn];
int stk[maxn * 2], deep[maxn];
int anc[maxn][19], cost[maxn][19];
int dfn[maxn], num, vis[maxn];
ll dp[maxn];
void dfs(int x, int fa, int c, int d) {
deep[x] = d; anc[x][0] = fa;
dfn[x] = num++; cost[x][0] = c;
for(int i = 1; i < 19; i++) {
int t = anc[x][i - 1];
if(t == -1) break;
anc[x][i] = anc[t][i - 1];
cost[x][i] = min(cost[x][i - 1], cost[t][i - 1]);
}
for(int i = 0; i < G[x].size(); i++) {
int v = G[x][i].first, w = G[x][i].second;
if(v == fa) continue;
dfs(v, x, w, d + 1);
}
}
int min_xy, vec[maxn * 4];
int query_anc(int x, int y) {
min_xy = INF;
if(deep[x] > deep[y]) swap(x, y);
int lg = 0; while((1 << lg) <= deep[y]) lg++; lg--;
for(int i = lg; i >= 0; i--) {
if(deep[y] - deep[x] < (1 << i)) continue;
min_xy = min(min_xy, cost[y][i]); y = anc[y][i];
}
if(y == x) return x;
for(int i = lg; i >= 0; i--) {
int px = anc[x][i], py = anc[y][i];
if(px == py) continue;
min_xy = min(min_xy, min(cost[x][i], cost[y][i]));
x = px; y = py;
}
min_xy = min(min_xy, min(cost[x][0], cost[y][0]));
return anc[x][0];
}
bool cmp(int x, int y) { return dfn[x] < dfn[y]; } ///关键点按照dfs序排序
void add_edge(int x, int y) {
min_xy = INF; query_anc(x, y);
g[x].push_back(pa(y, min_xy));
g[y].push_back(pa(x, min_xy));
}
void build_virtual_tree(int sz, int &cnt) {
sort(vec, vec + sz, cmp);
int tot = 0; stk[tot++] = 0;
for(int i = 0; i < sz; i++) {
int x = vec[i], lca = query_anc(x, stk[tot - 1]);
vis[x] = 1;
if(lca == stk[tot - 1]) stk[tot++] = x;
else {
while(tot - 2 >= 0 && deep[stk[tot - 2]] >= deep[lca]) {
int u = stk[tot - 1], v = stk[tot - 2];
add_edge(u, v); tot--;
}
if(stk[tot - 1] != lca) {
int u = stk[tot - 1]; tot--;
add_edge(u, lca);
stk[tot++] = lca; vec[cnt++] = lca;
}
stk[tot++] = x;
}
}
for(int i = 0; i < tot - 1; i++) {
int u = stk[i], v = stk[i + 1];
add_edge(u, v); vec[cnt++] = stk[i];
}
vec[cnt++] = stk[tot - 1];
}
ll solve(int x, int fa) {
if(g[x].size() == 1) { dp[x] = INF; return dp[x]; }
ll tot_cost = 0;
for(int i = 0; i < g[x].size(); i++) {
int v = g[x][i].first;
if(v == fa || !v) continue;
solve(v, x);
min_xy = INF; query_anc(x, v);
if(vis[v]) tot_cost += min_xy;
else tot_cost += min((ll)min_xy, dp[v]);
}
return dp[x] = tot_cost;
}
int main() {
while(scanf("%d", &n) != EOF) {
memset(anc, -1, sizeof anc); num = 0;
memset(vis, 0, sizeof vis);
for(int i = 0; i < maxn; i++) {
G[i].clear(); g[i].clear();
}
for(int i = 1; i < n; i++) {
int u, v, c;
scanf("%d %d %d", &u, &v, &c);
G[u].push_back(pa(v, c));
G[v].push_back(pa(u, c));
}
dfs(1, 0, 0, 1);
int lca = query_anc(1, 6);
scanf("%d", &m);
while(m--) {
int cnt; scanf("%d", &cnt);
for(int i = 0; i < cnt; i++)
scanf("%d", &vec[i]);
vec[cnt++] = 1;
build_virtual_tree(cnt, cnt);
g[0].clear();
ll ans = solve(1, 0);
for(int i = 0; i < cnt; i++) { g[vec[i]].clear(); vis[vec[i]] = 0; }
printf("%lld\n", ans);
}
}
return 0;
}