- 题目链接 : http://acm.hdu.edu.cn/showproblem.php?pid=1863
- 题意 : 给定n个道路和m个村庄,每条道路有一个权值,求最小的权值和,使得村庄间可以互相到达。
- 思路 :本题是最小生成树的思想,这里我用Kruskal算法。
对于一个无向带权图,每个节点都是独立的。步骤:
1)将所有边按权值排序
2)选取最小权值的边,判断边两端的点是不是属于一个集合,如果是就不选取这条边;如果不是,就将这两点并到一个集合,并选取这条边。(其中用到了 并查集)。
可以见得,Kruskal使用了并查集和贪心算法结合。
例如
如图所示,在遍历到权值为6的边时,3 和 2两点已经在一个集合中了,所以最小生成树就生成了。
- 代码:
#include "bits/stdc++.h"
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define fori(i,l,u) for(int i = l;i < u;i++)
#define forj(j,l,u) for(int j = l;j < u;j++)
#define F first
#define S second
#define pb push_back
#define mk make_pair
typedef long long ll;
typedef pair<int, int> pi;
typedef pair<string, int> ps;
typedef vector<int> vi;
typedef vector<string> vs;
typedef vector<pi> vpi;
const int maxn = 1e2 + 6;
int node[maxn],rk[maxn];
int n,m;
typedef struct {
int u;
int v;
int w;
}L;
L l[maxn];
void init(){
fori(i, 1, n+1){
node[i] = i;
rk[i] = 0;
}
}
int find(int x){
if (x == node[x]) {
return x;
}
return node[x] = find(node[x]);
}
void merge(int x,int y){
x = find(x);
y = find(y);
if (rk[x] < rk[y]) {
node[x] = y;
}
else{
node[y] = x;
if (rk[x] == rk[y]) {
rk[x] ++;
}
}
}
int compare(const L &x,const L &y){
return x.w < y.w;
}
int Kruskal(int n,int m){
int nEdge = 0,res = 0;
sort(l, l+n, compare);
for (int i = 1; i < n+1 && nEdge != m-1; i++) {
if (find(l[i].u) != find(l[i].v)) {
merge(l[i].u,l[i].v);
res += l[i].w;
nEdge ++;
}
}
if (nEdge < m-1) {
res = -1;
}
return res;
}
bool compare(L x,L y){
return x.w < y.w;
}
int main()
{
int ans;
// freopen("1.txt", "r", stdin);
while (~scanf("%d%d",&n,&m) && n) {
init();
fori(i, 1, n+1){
scanf("%d%d%d",&l[i].u,&l[i].v,&l[i].w);
}
ans = Kruskal(n, m);
if (ans == -1) {
cout<<"?"<<endl;
}
else cout<<ans<<endl;
}
return 0;
}
- 遇到的问题 :
1)注意这道题 maxn = 1e2+ 5左右,因为如果按照m < 100,会wa。而且对结构体的排序可能不会,就compare特殊一下就行。
2)为什么Kruskal里,nEdge = m - 1就跳出循环,因为这是最小生成树的性质。假设有这样一种情况 :
按权值贪心加,到3条边的时候,就能连起四个顶点,所以就跳出。这是最小生成树。