题意:给你一个有n(2<=n<=100000)个节点的树,树中每条边都有一个权值。然后再给你k(2<=k<=n)个点,表示这些点上有一个机器人。最后让你删去一些边使任意两个机器人都不能互达,且所删边的权值之和要最小。
思路:我最开始想到的是:
1、将边按权值由小到大排序。2、计算每条边连接的两个子树中分别有多少个机器人。
3、然后,枚举每条边,如果该条边所连接的两个子树中都有机器人,则将该条边删除。
4、重复步骤2和步骤3,直到枚举完所有的边。
5、所删除的边的权值之和就是要求的结果。
但是,这样做时间复杂度太高,主要是第2步花了太多的时间。后来,发现,完全可以反过来做,思路如下:
1、初始化每个节点为一个集合,并记录每个集合中机器人的数目。
2、将边按权值由大到小排序。
3、枚举每条边,如果该边两端点所在的集合最多只有一个机器人,则合并这两个集合(用并查集),这条边不用删除。否则,这条边要删除。
4、重复步骤3直到枚举完所有边。
5、所删除的边的权值之和就是要求的结果。
代码如下:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
const int maxn = 100010;
int t, n, k;
int mach[maxn], father[maxn];
struct Edge {
int u, v, c;
}edge;
vector<Edge> vv;
bool cmp(Edge a, Edge b)
{
return a.c > b.c;
}
void initSet()
{
for (int i = 0; i < maxn; ++i) {
father[i] = i;
mach[i] = 0;
}
}
int find(int x)
{
if (x != father[x]) {
father[x] = find(father[x]);
}
return father[x];
}
void merge(int a, int b)
{
a = find(a);
b = find(b);
if (a == b) return ;
if (a < b) {
father[b] = a;
mach[a] += mach[b];
} else {
father[a] = b;
mach[b] += mach[a];
}
}
int main()
{
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &k);
vv.clear();
initSet();
int u, v, c;
__int64 ans = 0;
for (int i = 0; i < n - 1; ++i) {
scanf("%d%d%d", &u, &v, &c);
edge.u = u; edge.v = v; edge.c = c;
vv.push_back(edge);
ans += c;
}
for (int i = 0; i < k; ++i) {
scanf("%d", &u);
mach[u] = 1;
}
sort(vv.begin(), vv.end(), cmp);
for (int i = 0; i < n - 1; ++i) {
u = find(vv[i].u);
v = find(vv[i].v);
if (mach[u] + mach[v] <= 1) {
merge(u, v);
ans -= vv[i].c;
}
}
printf("%I64d\n", ans);
}
return 0;
}