新的开始
题目链接:ybt高效进阶3-2-2
题目大意
有一个图,要你让所有点所在的连通块都至少有一个标记点。
两个点相连有一个规定的费用,把一个点标记也有费用。
要费用最小,输出最小费用。
思路
这道题看到判断连通,我们就想到并查集。然后最小,就想到最小生成树。
但是它不一定只选一个点标记,而且如果标记了不止一个点,整个图就可以不是整个连通的。
那怎么办呢?
那我们想想如果标记了不止一个点,那这些点之间会有怎样的关系。
可以想到,它们之间是可以当做任意连通的,那你可以把每个标记的点之间都弄上一条边,权值为
0
0
0。
但你如果要想最小生成树那样搞的话,就不能像上面那样不停地加新的边。
那我们考虑这样弄:
我们把标记点也当做一条边,权值就是标记的费用,然后从自己连向自己。
那然后你就把所有边排序,像最小生成树一样搞。正常的边就正常地搞。对于标记的边我们分两种:
第一种,第一次遇到标记的边,那因为整个图中一定要标记至少一个,那我们是一定要标记这个点的。(因为你按费用从小到大排序,那你第一个找到哦的标记的一定就是所有中权值最小的)
第二种,不止第一次,那它其实就相当于跟前面标记过的点连边。那根据这样的方法,前面的标记过的点一定是连通了的,那我们只要随便选一个就可以了。当然,如果已经连通,就没有必要标记。
还有循环结束的条件就多了一个,不止是正常的边的数量要是 n − 1 n-1 n−1,还要至少有一个点被标记。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
struct node {
int x, y, z;
}a[90001 + 100001];
int n, now, num, last, fa[301], line;
long long ans;
bool cmp(node x, node y) {
return x.z < y.z;
}
int find(int now) {
if (fa[now] == now) return now;
return fa[now] = find(fa[now]);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i].z);
a[i].x = a[i].y = i;//我们将标记点也看做一条边,连向自己
fa[i] = i;
}
now = n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &a[now + 1].z);
if (i > j) {
now++;
a[now].x = i;
a[now].y = j;
}
}
}
sort(a + 1, a + now + 1, cmp);
for (int i = 1; i <= now; i++) {
if (a[i].x == a[i].y) {
if (!last) {//至少要一个标记点
last = a[i].x;
ans += 1ll * a[i].z;
}
else {
int X = find(a[i].x), Y = find(last);
if (X == Y) continue;//如果跟之前标记的点在一个连通块里面就没必要标记
else {
fa[X] = Y;
last = a[i].x;
ans += 1ll * a[i].z;
}
line++;
if (line == n - 1 && last) break;
}
}
else {//正常的最小生成树
int X = find(a[i].x), Y = find(a[i].y);
if (X == Y) continue;
fa[X] = Y;
ans += 1ll * a[i].z;
line++;
if (line == n - 1 && last) break;
}
}
printf("%lld", ans);
return 0;
}