最小生成树
我们定义无向连通图的 最小生成树(Minimum Spanning Tree,MST)为边权和最小的生成树。
注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。
一、模板
prim 算法模板
include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const int N = 1e6 + 10;
int n, m, k;
int dist[N], vis[N];
//int h[N], e[N], ne[N], idx, v[N];
int e[2005][2005];
int res = 0;
int prime()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i++)
{
int mi = INF;
int t = -1;
for (int j = 1; j <= n; j++)
if (!vis[j] && (t == -1 || dist[j] < mi))
{
t = j;
mi = dist[j];
}
if (dist[t] == INF)
return INF;
vis[t] = 1;
res += dist[t];
for (int j = 1; j <= n; j++)
dist[j] = min(dist[j], e[t][j]);
}
return res;
}
int main()
{
cin.tie(0);
cin >> n >> m;
memset(e, 0x3f, sizeof e);
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
e[a][b] = e[b][a] = min(e[a][b], c);
}
int t = prime();
if (t == INF)
puts("orz");
else
cout << t << endl;
}
kruskal 模板
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
const int INF = 1e9;
const int N = 1e6 + 10;
int n, m, k, l;
int p[N];
//int h[N], e[N], ne[N], idx, v[N];
int d[212][210];
struct kk
{
int a, b, v;
bool operator<(const kk &q) const
{
return v < q.v;
}
} ed[N];
int find(int x)
{
if(p[x] != x)
p[x] = find(p[x]);
return p[x];
}
int main()
{
int a, b, c;
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> a >> b >> c;
ed[i] = {a,b,c};
}
sort(ed,ed + m);
for(int i = 1 ;i <= n;i++)
p[i] = i;
int cnt = 0,res = 0;
for(int i = 0 ; i < m;i++)
{
a = ed[i].a,b = ed[i].b,c = ed[i].v;
a = find(a),b = find(b);
if(a != b)
{
p[a] = b;
cnt++;
res += c;
}
}
if(cnt < n - 1)
puts("orz");
else cout<<res<<endl;
}
二、应用
很裸的生成树,直接套板子即可(点数,边数较少时建议使用prim算法)
参考代码
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N = 110;
int a[N][N];
int dist[N];
bool st[N];
int n;
int prim(){
int ans = 0;
memset(dist, 0x7f, sizeof(dist));
dist[1] = 0;
for(int i = 0;i < n ; i ++){
int t = -1;
for(int j = 1; j <= n ; j ++){
if(!st[j] && (t == -1 || dist[t] > dist[j])){
t = j;
}
}
ans += dist[t];
st[t] = true;
for(int j = 1; j <= n ; j ++){
if(dist[j] > a[t][j]){
dist[j] = a[t][j];
}
}
}
return ans;
}
signed main(){
scanf("%lld",&n);
for(int i = 1; i <= n ; i ++){
for(int j = 1; j <= n ; j ++){
scanf("%lld",&a[i][j]);
}
}
printf("%lld\n",prim());
}
题意:在无向图中删除一些边,但是不能影响整张图的连通性,输出删除边权和的最大值。
思路:删除边权和最大即在保证连通性的条件下使得保留的边权和最小,直接套模板即可(点数,边数较大时,需使用kruskal算法)。
参考代码
#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N = 110, M = 210;
struct edge{
int u, v, w;
bool operator< (const edge &t)const{
return w < t.w;
}
}edges[M];
int n, m, sum;
int f[N];
int find(int x){
if(f[x] != x){
f[x] = find(f[x]);
}
return f[x];
}
signed main(){
scanf("%lld%lld",&n, &m);
for(int i = 1; i <= n; i ++){
f[i] = i;
}
for(int i = 0; i < m ; i ++){
int a, b, c;
scanf("%lld%lld%lld",&a, &b, &c);
edges[i] = {a, b, c};
}
sort(edges, edges + m);
for(int i = 0; i < m ; i ++){
int a = find(edges[i].u);
int b = find(edges[i].v);
int w = edges[i].w;
if(a != b){
f[a] = b;
}
else{
sum += w;
}
}
printf("%lld\n", sum);
}
题意: 在无向图中选择一些边改造,这些改造的边能够将所有的点连通起来。
思路: 不难看出当选择的边构成一颗树时所选的变数最少即n-1条边,所以只需对原图做一遍最小生成树,在加边的过程中记录最大值即可。
参考代码
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int N = 310, M = 1e5 + 10;
struct edge{
int u, v, w;
}edges[M];
int n, m, p[N];
bool cmp(edge x, edge y){
return x.w < y.w;
}
int find(int x){
if(p[x] != x){
p[x] = find(p[x]);
}
return p[x];
}
signed main(){
scanf("%lld%lld",&n, &m);
for(int i = 1; i <= n ; i ++){
p[i] = i;
}
for(int i = 0; i < m ; i ++){
int a, b, c;
scanf("%lld%lld%lld",&a, &b, &c);
edges[i] = {a, b, c};
}
sort(edges, edges + m, cmp);
int max1, cnt = 0;
for(int i = 0; i < m ; i ++){
int a = find(edges[i].u);
int b = find(edges[i].v);
int w = edges[i].w;
if(a != b){
p[a] = b;
cnt ++;
if(cnt == n - 1){
max1 = w;
}
}
}
printf("%lld %lld\n", n - 1, max1);
}
题意:在p个哨所中选择至多k个电话,每个哨所之间的通话距离都是在D范围内在保证连通性的条件下使得D最小。
思路:可以建造k个卫星电话即整个图可以分成k的连通块,设最初连通块个数为p,按照边权从小到大枚举所有边构建最小生成树,在构建最小生成树的过程中每当有两个边合并时连通块个数减一并记录最大边权ans,若连通块个数减少到k个时ans即为答案。
参考代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef pair<int, int> PII;
const int N = 510;
struct edge{
int u, v;
double w;
bool operator< (const edge &a) const
{
return w < a.w;
}
}edges[N * N];
PII q[N * N];
int p[N], n, m, k;
double get_dist(PII a, PII b){
int dx = a.first - b.first;
int dy = a.second - b.second;
return sqrt(dx * dx * 1.0 + dy * dy * 1.0);
}
int find(int x){
if(p[x] != x){
p[x] = find(p[x]);
}
return p[x];
}
int main(){
scanf("%d%d",&k, &n);
for(int i = 0; i < n ; i ++){
int a, b;
scanf("%d%d",&a, &b);
q[i] = {a, b};
}
for(int i = 0; i < n ; i ++){
for(int j = 0; j < i ; j ++){
if(i != j){
edges[m ++] = {i, j, get_dist(q[i], q[j])};
}
}
}
sort(edges, edges + m);
for(int i = 0; i < n ; i ++){
p[i] = i;
}
/*记录连通块数量*/
int cnt = n;
/*记录满足连通块为k条件下的最小边*/
double ans = 0;
/*
for(int i = 0; i < m ; i ++){
printf("(%d %d %lf)\n",edges[i].u, edges[i].v, edges[i].w);
}*/
for(int i = 0; i < m ; i ++){
if(cnt <= k){
break;
}
int a = find(edges[i].u);
int b = find(edges[i].v);
double w = edges[i].w;
if(a != b){
p[a] = b;
ans = w;
cnt --;
}
}
printf("%.2lf\n", ans);
return 0;
}
次小生成树
次小生成树有严格次小生成树和非严格次小生成树。对于一个无向图来说最小生成树可能树不唯一的,我们在构建最小生成树之后,选择一条非树边替换一条树边(不破坏连通性),这条非树边权值一定大于或等于被替换的树边。若所有新生成的生成树中权值和最小的生成树(整个图的次小生成树)的权值等于最小生成树的权值和则为非严格,否则为严格次小生成树。
例题推荐:
参考代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N = 510, M = 1e4 + 10;
struct edge{
int a, b, w;
bool flag;
bool operator< (const edge &t) const{
return w < t.w;
}
}edges[M];
int n, m;
int p[N], dist1[N][N], dist2[N][N];
int h[N], e[N << 1], ne[N << 1], w[N << 1], idx;
void add(int a, int b, int c){
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++;
}
int find(int x){
if(p[x] != x){
p[x] = find(p[x]);
}
return p[x];
}
void dfs(int u, int fa, int maxd1,int maxd2, int d1[], int d2[]){
d1[u] = maxd1;
d2[u] = maxd2;
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if(j != fa){
int td1 = maxd1;
int td2 = maxd2;
if(w[i] > td1){
td2 = td1;
td1 = w[i];
}
else if(w[i] < td1 && w[i] > td2){
td2 = w[i];
}
dfs(j, u, td1, td2, d1, d2);
}
}
}
signed main(){
scanf("%lld%lld",&n, &m);
memset(h, -1, sizeof(h));
for(int i = 0; i < m ; i ++){
scanf("%lld%lld%lld",&edges[i].a, &edges[i].b, &edges[i].w);
edges[i].flag = false;
}
sort(edges, edges + m);
for(int i = 1; i <= n; i ++){
p[i] = i;
}
int sum = 0, ans = 1e18;
for(int i = 0; i < m ; i ++){
int a = edges[i].a;
int b = edges[i].b;
int w = edges[i].w;
int pa = find(a);
int pb = find(b);
if(pa != pb){
p[pa] = pb;
sum += w;
edges[i].flag = true;
add(a, b, w);
add(b, a, w);
}
}
/*以i为起点到各个根节点的距离(在生成树中)*/
for(int i = 1; i <= n ; i ++){
dfs(i, -1, -1e9, -1e9, dist1[i], dist2[i]);
}
/*
for(int i = 0; i < m ; i ++){
printf("%lld %lld %lld %lld\n",edges[i].a, edges[i].b, edges[i].w, edges[i].flag);
}
*/
for(int i = 0; i < m ; i ++){
if(!edges[i].flag){
int a = edges[i].a;
int b = edges[i].b;
int w = edges[i].w;
int t;
/*严格次小成生树*/
if(w > dist1[a][b]){
t = sum + w - dist1[a][b];
}
else if(w > dist2[a][b]){
t = sum + w - dist2[a][b];
}
ans = min(ans, t);
}
}
printf("%lld\n", ans);
}
参考代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define int long long
using namespace std;
const int N = 3e5 + 10, M = 3e5 + 10, INF = 1e18;
int f[N][20], dist1[N][20], dist2[N][20];
int h[N], e[M], ne[M], w[M], idx;
int p[N], depth[N], q[N];
int n, m, sum;
struct edge{
int a, b, w;
bool flag;
bool operator< (const edge &t)const{
return w < t.w;
}
}edges[M];
void add(int a, int b, int c){
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++;
}
int find(int x){
if(p[x] != x){
p[x] = find(p[x]);
}
return p[x];
}
int kruskal(){
int res = 0;
sort(edges, edges + m);
for(int i = 1; i <= n ; i ++){
p[i] = i;
}
for(int i = 0; i < m ; i ++){
int pa = find(edges[i].a);
int pb = find(edges[i].b);
int w = edges[i].w;
if(pa != pb){
res += w;
p[pa] = pb;
edges[i].flag = true;
}
}
return res;
}
void bulid(){
memset(h, -1, sizeof(h));
for(int i = 0; i < m ; i ++){
if(edges[i].flag){
int a = edges[i].a;
int b = edges[i].b;
int w = edges[i].w;
add(a, b, w);
add(b, a, w);
}
}
return ;
}
void bfs(){
memset(depth, 0x3f, sizeof(depth));
depth[0] = 0;
depth[1] = 1;
int hh = 0, tt = 0;
q[0] = 1;
while(hh <= tt){
int t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i]){
int j = e[i];
if(depth[j] > (depth[t] + 1)){
depth[j] = depth[t] + 1;
q[++ tt] = j;
f[j][0] = t;
dist1[j][0] = w[i];
dist2[j][0] = -INF;
for(int k = 1; k <= 16; k ++){
int anc = f[j][k - 1];
f[j][k] = f[anc][k - 1];
int distance[4] = {dist1[j][k - 1], dist2[j][k - 1], dist1[anc][k - 1], dist2[anc][k - 1]};
dist1[j][k] = dist2[j][k] = -INF;
for(int u = 0; u < 4; u ++){
int dd = distance[u];
if(dd > dist1[j][k]){
dist2[j][k] = dist1[j][k];
dist1[j][k] = dd;
}
else if(dd < dist1[j][k] && dd >dist2[j][k]){
dist2[j][k] = dd;
}
}
}
}
}
}
}
int lca(int a, int b, int w){
static int distance[N * 2];
int cnt = 0;
if(depth[a] < depth[b]){
swap(a, b);
}
for(int k = 16; k >= 0; k --){
if(depth[f[a][k]] >= depth[b]){
distance[cnt ++] = dist1[a][k];
distance[cnt ++] = dist2[a][k];
a = f[a][k];
}
}
if(a != b){
for(int k = 16; k >= 0; k --){
if(f[a][k] != f[b][k]){
distance[cnt ++] = dist1[a][k];
distance[cnt ++] = dist2[a][k];
distance[cnt ++] = dist1[b][k];
distance[cnt ++] = dist2[b][k];
a = f[a][k];
b = f[b][k];
}
}
distance[cnt ++] = dist1[a][0];
distance[cnt ++] = dist1[b][0];
}
int d1 = -INF, d2 = -INF;
for(int i = 0; i < cnt ; i ++){
int dd = distance[i];
if(dd > d1){
d2 = d1;
d1 = dd;
}
else if(dd < d1 && dd > d2){
d2 = dd;
}
}
if(w > d1){
return w - d1;
}
if(w > d2){
return w - d2;
}
return INF;
}
signed main(){
scanf("%lld%lld",&n, &m);
for(int i = 0; i < m ; i ++){
int a, b, c;
scanf("%lld%lld%lld",&a, &b, &c);
edges[i] = {a, b, c};
}
sum = kruskal();
bulid();
bfs();
int res = 1e18;
for(int i = 0; i < m ; i ++){
if(!edges[i].flag){
int a = edges[i].a;
int b = edges[i].b;
int w = edges[i].w;
res = min(res, sum + lca(a, b, w));
}
}
printf("%lld\n",res);
}