最小生成树
- 给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫做生成树。如果边上有权值,那么使得边权和最小的生成树叫做最小生成树。
prim算法 O ( E ∗ l o g ( V ) ) ) O(E*log(V))) O(E∗log(V)))
- 首先从某个顶点出发,贪心的选择和当前顶点相连权值的最小的顶点,把该顶点加入集合中去。直到所有顶点都加入集合为止
模板:
const int MAX_V=1999;
const int INF = 99999;
int cost[MAX_V][MAX_V];//cost[i][j]表示顶点i和j之间的边的权值
int mincost[MAX_V];//mincost[i]表示从集合X出发的边到顶点i的最小权值(X是生成树顶点的集合)
bool used[MAX_V];
int V;//V表示顶点数量
int prim() {
for(int i = 0; i < V; i++) {
mincost[i] = INF;
used[i] = false;
}
mincost[0] = 0;
int res = 0;
while(true) {
int v = -1;
for(int u = 0; u < V; u++) {//找到需要加入当前X集合外的权值最小的顶点v
if(!used[u] && (v == -1 || mincost[u] < mincost[v]))
v = u;
}
if(v == -1)
break;
used[v] = true;
res += mincost[v];//把权值加入进去
for(int u = 0; u < V; u++)//更新,顶点v到所有其他可连接的顶点的距离
mincost[u] = min(mincost[u], cost[v][u]);
}
return res;
}
例题:洛谷
P
1194
P1194
P1194
问题描述:
又到了一年一度的明明生日了,明明想要买
B
B
B样东西,巧的是,这
B
B
B样东西价格都是
A
A
A元。
但是,商店老板说最近有促销活动,也就是:如果你买了第
I
I
I样东西,再买第
J
J
J样,那么就可以只花
K
I
,
J
K_{I,J}
KI,J元,更巧的是,
K
I
,
J
K_{I,J}
KI,J竟然等于
K
J
,
I
K_{J,I}
KJ,I 。
现在明明想知道,他最少要花多少钱。
输入
第一行两个整数,
A
,
B
A,B
A,B。
接下来
B
B
B行,每行
B
B
B个数,第
I
I
I行第
J
J
J个为
K
I
,
J
K_{I,J}
KI,J 。
我们保证
K
I
,
J
=
K
J
,
I
K_{I,J}=K_{J,I}
KI,J=KJ,I并且
K
I
,
I
=
0
K_{I,I}=0
KI,I=0。
特别的,如果
K
I
,
J
=
0
K_{I,J}=0
KI,J=0,那么表示这两样东西之间不会导致优惠。
输出
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz
样例输入
3 3
0 2 4
2 0 2
4 2 0
样例输出
7
#include <cstdio>
#include <algorithm>
#define INF 0x3f3f3f3f
#define MAXN_V 505
using namespace std;
int cost[MAXN_V][MAXN_V];
int mincost[MAXN_V];
bool used[MAXN_V];
int V, a;
int prim() {
for(int i = 1; i <= V; i++) {
mincost[i] = INF;
used[i] = false;
}
mincost[1] = 0;
int res = 0;
while(1) {
int v = -1;
for(int i = 1; i <= V; i++) {
if(!used[i] && (v == -1 || mincost[i] < mincost[v]))
v = i;
}
if(v == -1) break;
used[v] = 1;
if(mincost[v] < a) //如果当前加入的权值比初始价格便宜才加,否则加a
res += mincost[v];
else
res += a;
for(int i = 1; i <= V; i++)
mincost[i] = min(mincost[i], cost[v][i]);
}
return res;
}
int main() {
scanf("%d %d", &a, &V);
for(int i = 1; i <= V; i++)
for(int j = 1; j <= V; j++) {
scanf("%d", &cost[i][j]);
if(i == j) cost[i][j] = INF;
else if(cost[i][j] == 0) cost[i][j] = a;
}
int ans = prim();
printf("%d\n", ans+a);
return 0;
}
kruskal算法 ( O ( E ∗ l o g ( V ) ) ) (O(E*log(V))) (O(E∗log(V)))
- 按照边的权值的顺序从小到大查看一遍,如果不产生圈(通过并查集来判断是否会产生连通分量,若有则会产生圈),就把当前这个边加入到生成树中
模板:
const int MAX_E = 1000;
struct edge {
int u, v, cost;
};
bool cmp(const edge &a, const edge &b) {
return a.cost < b.cost;
}
edge es[MAX_E];
int V, E;
int kruskal() {
sort(es, es+E, cmp);
init_union_find(V);//初始化并查集
int res = 0;
for(int i = 0; i < E; i++) {
edge e = es[i];
if(!same(e.u,e.v)) { //如果当前要加入的边不在集合中,则可加进来
unite(e.v,e.u);
res += cost;
}
}
return res;
}
例题:洛谷
P
3366
P3366
P3366
问题描述:
给出一个无向图,求出最小生成树,如果该图不连通,则输出orz
输入
第一行包含两个整数
N
、
M
N、M
N、M,表示该图共有
N
N
N个结点和
M
M
M条无向边。
(
N
<
=
5000
,
M
<
=
200000
)
(N<=5000,M<=200000)
(N<=5000,M<=200000)
接下来
M
M
M行每行包含三个整数
X
i
、
Y
i
、
Z
i
X_i、Y_i、Z_i
Xi、Yi、Zi,表示有一条长度为
Z
i
Z_i
Zi的无向边连接结点
X
i
、
Y
i
X_i、Y_i
Xi、Yi
输出
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz
样例输入
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
样例输出
7
#include <cstdio>
#include <algorithm>
#define MAXN 200000 + 100
using namespace std;
struct Edge {
int u, v, cost;
Edge(){}
Edge(int _u, int _v, int _cost):u(_u), v(_v), cost(_cost){}
}e[MAXN];
bool cmp(Edge a, Edge b) {
return a.cost < b.cost;
}
int V, E, n, cnt, p[MAXN], k;
int find(int x) { //判断是否是在同一个集合中
if(x != p[x])
return p[x] = find(p[x]);
else
return x;
}
int kruskal() {
for(int i = 0; i <= k; i++) // 初始化并查集
p[i] = i;
sort(e+1, e+1+k, cmp);
int res = 0;
for(int i = 1; i <= k; i++) {
Edge t = e[i];
int x = find(t.u);
int y = find(t.v);
if(x != y) { //如果不在一个集合中,就把这条边加进这个集合然后加上权值
p[x] = y;
cnt++;
res += t.cost;
}
}
return res;
}
int main() {
scanf("%d%d", &V, &E);
for(int i = 1; i <= E; i++) {
k++;
scanf("%d%d%d", &e[k].u, &e[k].v, &e[k].cost);
}
int ans = kruskal();
if(cnt != V-1) printf("orz\n");
else printf("%d", ans);
return 0;
}