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;
}