kruskal算法相当简单直观~还比prim好用~
应用一, 求最大权值最小的最小生成树.
仔细想一想, 最小生成树的最大权值就是最小的.
应用二, 求次小生成树.
先求一个最小生成树, 然后枚举这个最小生成树的每一条边, 然后计算分别删去这个边的图的最小生成树, 一共有n - 1个, 次小生成树一定是这几个.
poj1679的问图的最小生成树是否为一, 可以先求最小生成树, 在求次小生成树, 如果两个生成树的权值相等, 则说明最小生成树不唯一.
求次小生成树的优化:
引入概念:
若 T 是图 G 的一个生成树, 对于非树边 a 和树边 b , 插入边 a 并删除边 b 的操作记为(+a, -b).
如果 T + a - b 仍然是一个生成树, 称(+a, -b)是 T 的一个可行交换.
设T为图G的一个生成树, 由T进行一个可行交换得到的新的生成树集合称为T的邻集.
定理: 次小生成树一定在最小生成树的邻集中.
优化方法:
1. 通过O(n^2)求出MAX[i][j]数组, 代表着i到j之间的路径上的最大边权.
2. 通过倍增思想, 在O(elge)的时间内求出MAX[i][j], 代表着i到i的2^j辈祖先路径的最大边权.再通过LCA可以求出任意两个节点路径之间的最大边权了.
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <cstdlib>
#include <cstdio>
#include <cmath>
using namespace std;
const int MAXN = 120;
const int MAXM = 5500;
int faz[MAXN];
int n, m;
struct Edge{
int from, to, weight;
Edge(int from, int to, int weight): from(from), to(to), weight(weight){}
bool operator<(const Edge & rhs) const{
return weight < rhs.weight;
}
};
int find(int x){
if(x == faz[x]){
return x;
}
return faz[x] = find(faz[x]);
}
void merge(int x, int y){
x = find(x);
y = find(y);
faz[x] = y;
}
void init(){
for(int i = 0; i < MAXN; ++i){
faz[i] = i;
}
}
int main(){
int T;
scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &m);
int x, y, w;
vector<Edge> v;
for(int i = 0; i < m; ++i){
scanf("%d%d%d", &x, &y, &w);
v.push_back(Edge(x, y, w));
}
sort(v.begin(), v.end());
init();
vector<int> flag;
int mst_cost = 0;
for(int i = 0; i < v.size(); ++i){
int x = v[i].from, y = v[i].to;
if(find(x) != find(y)){
merge(x, y);
mst_cost += v[i].weight;
flag.push_back(i);
}
}
int second_cost = 0x3f3f3f3f;
for(int i = 0; i < flag.size(); ++i){
int cur_cost = 0;
init();
for(int j = 0; j < v.size(); ++j){
if(flag[i] != j){
int x = v[j].from, y = v[j].to, w = v[j].weight;
if(find(x) != find(y)){
merge(x, y);
cur_cost += w;
}
}
}
int chief = find(1);
for(int i = 2; i <= n; ++i){
if(find(i) != chief){
cur_cost = 0x3f3f3f3f;
}
}
second_cost = min(second_cost, cur_cost);
}
if(mst_cost == second_cost){
printf("Not Unique!\n");
}
else{
printf("%d\n", mst_cost);
}
}
return 0;
}
应用三, 求最大权值和最小权值的差最小的生成树.
先求最小生成树, 然后把这个生成树中的最小边从图中删除, 在求一次最小生成树, 再把这个生成树的最小边从图中删除...重复这个过程, 删去的边不能恢复.
这其中一定有一颗最大权值和最小权值的差最小的生成树, 这很像尺取, 反过来求最大生成树也应该可以.
注意这里算法的终止条件: 当剩余的边不足以使点联通了.
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <cstdlib>
#include <cstdio>
#include <cmath>
using namespace std;
const int MAXN = 120;
const int MAXM = 5500;
int faz[MAXN];
struct Edge{
int from, to, limit;
Edge(int from, int to, int limit): from(from), to(to), limit(limit){}
bool operator<(const Edge & rhs) const{
return limit < rhs.limit;
}
};
int find(int x){
int root = x;
while(root != faz[root]){
root = faz[root];
}
int temp;
while(x != root){
temp = faz[x];
faz[x] = root;
x = temp;
}
return root;
}
void merge(int x, int y){
x = find(x);
y = find(y);
faz[x] = y;
}
void init(){
for(int i = 0; i < MAXN; ++i){
faz[i] = i;
}
}
int main(){
int n, m;
int x, y, l;
scanf("%d%d", &n, &m);
int rst = 0x3f3f3f3f;
vector<Edge> v;
for(int i = 0; i < m; ++i){
scanf("%d%d%d", &x, &y, &l);
v.push_back(Edge(x, y, l));
}
sort(v.begin(), v.end());
int maxn = -0x3f3f3f3f, minn = 0x3f3f3f3f;
vector<int> smallest;
int counter = 0;
init();
bool trigger = true;
for(int i = 0; i < v.size(); ++i){
int x = v[i].from, y = v[i].to, l = v[i].limit;
if(find(x) != find(y)){
merge(x, y);
++counter;
maxn = max(maxn, l);
minn = min(minn, l);
if(trigger){
smallest.push_back(i);
trigger = false;
}
}
}
if(counter != n - 1){
printf("-1\n");
return 0;
}
rst = min(rst, maxn - minn);
while(true){// 枚举最小边
init();
int counter = 0;
maxn = -0x3f3f3f3f, minn = 0x3f3f3f3f;
trigger = true;
for(int i = 0; i < v.size(); ++i){
if(lower_bound(smallest.begin(), smallest.end(), i) != smallest.end()){
continue;
}
int x = v[i].from, y = v[i].to, l = v[i].limit;
if(find(x) != find(y)){
merge(x, y);
++counter;
maxn = max(maxn, l);
minn = min(minn, l);
if(trigger){
smallest.push_back(i);
trigger = false;
}
}
}
if(counter == n - 1){
rst = min(rst, maxn - minn);
}
else{
break;
}
}
printf("%d\n", rst);
return 0;
}