和 [JSOI2008]星球大战 类似的套路,都是运用 反向思维 的好题。
首先假设所有边都被破坏,然后减去一些不需要被破坏的边的花费就是答案。
注意我们要求答案最小,就一定要减去边权尽量大的边,因此我们就需要将边权从大到小排序后再处理。
讲一下具体判断一条边是否不需要被破坏的方式:
- 设一个数组 (vis_i),表示以 (i) 为代表的连通块中是否存在要被破坏的城市。(fa_i) 表示 (i) 号城市所在的集合的代表,初始 (fa_i=i)。
其实就是并查集 - 对于一条边的端点 (u)、(v),如果 (vis_{u 的祖先}) 与 (vis_{v 的祖先}) 不都为 true,那么这一条边就可以被破坏。还需要进行一些后续的操作。
代码写起来很简单。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
inline int gi()
{
int f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return f * x;
}
const int INF = 0x3f3f3f3f, N = 100003, M = N << 1;
int n, k;
int a[N];
int tot, head[N], ver[M], nxt[M], edge[M];
int fa[N];
LL sum;
bool vis[N];
struct Node
{
int u, v, w;
} x[N];
inline void add(int u, int v, int w)
{
ver[++tot] = v, edge[tot] = w, nxt[tot] = head[u], head[u] = tot;
}
inline bool cmp(Node x, Node y)
{
return x.w > y.w;
}
int getf(int u)
{
if (fa[u] == u) return u;
return fa[u] = getf(fa[u]);
}
int main()
{
n = gi(), k = gi();
for (int i = 1; i <= k; i+=1) a[i] = gi() + 1, vis[a[i]] = true;
for (int i = 1; i <= n; i+=1)
fa[i] = i;
for (int i = 1; i < n; i+=1)
{
int u = gi() + 1, v = gi() + 1, w = gi();
add(u, v, w), add(v, u, w);
x[i] = (Node){u, v, w};
sum += w;
}
sort(x + 1, x + n, cmp);
for (int i = 1; i < n; i+=1)
{
int u = getf(x[i].u), v = getf(x[i].v);
if (!(vis[u] && vis[v]))
{
fa[u] = v;
vis[v] |= vis[u];
sum -= x[i].w;
}
}
printf("%lld
", sum);
return 0;
}