YbtOJ 「图论」 第2章 最小生成树
最小生成树kruskal(无向图)
求一张图中边权最小 且包含所有点的树 将每一条边记录到结构体中 再将所有边按照边权排序 从小到大加边 用并查集维护节点之间的关系 如果遍历到当前这条边的两个节点在同一个集合内 就舍弃这条边 去找下一条小边
struct node
{
int u , v , w;
bool operator < ( const node &a ) const { return w < a.w; }
}a[N];
int find ( int x )
{
if ( fa[x] == x ) return x;
else return fa[x] = find ( fa[x] );
}
for ( int i = 1 ; i <= n ; i ++ ) fa[i]= i;
sort ( a + 1 , a + m + 1 );
for ( int i = 1 ; i <= m ; i ++ )
{
int fu = find ( a[i].u ) , fv = find ( a[i].v );
if ( fu != fv )
{
fa[fu] = fv;
ans += a[i].w;
}
}
A. 【例题1】繁忙都市
[题目描述]
城市 C 是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市 C 的道路是这样分布的:城市中有
- 改造的那些道路能够把所有的交叉路口直接或间接的连通起来。
- 在满足要求 1 的情况下,改造的道路尽量少。
- 在满足要求 1、2 的情况下,改造的那些道路中分值最大的道路分值尽量小。
任务:作为市规划局的你,应当作出最佳的决策,选择哪些道路应当被修建。
[输入格式]
第一行有两个整数
接下来
[输出格式]
两个整数
[算法分析]
裸题
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e5 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , maxx , n , m;
struct node
{
int u , v , w;
bool operator < ( const node &a ) const { return w < a.w; }
}a[N];
int find ( int x )
{
if ( fa[x] == x ) return x;
else return fa[x] = find ( fa[x] );
}
int main()
{
n = read() , m = read();
for ( int i = 1 ; i <= m ; i ++ ) a[i].u = read() , a[i].v = read() , a[i].w = read();
for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
sort ( a + 1 , a + m + 1 );
for ( int i = 1 ; i <= m ; i ++ )
{
int fu = find ( a[i].u ) , fv = find ( a[i].v );
if ( fu != fv )
{
fa[fu] = fv;
maxx = a[i].w;
}
}
printf ( "%d %d" , n - 1 , maxx );
return 0;
}
B. 【例题2】新的开始
[题目描述]
Farmer John 的农场缺水了。
他决定将水引入到他的
请求出 FJ 需要为使所有农场都与有水的农场相连或拥有水井所需要的最少钱数。
[输入格式]
第一行为一个整数
接下来
接下来
[输出格式]
输出最小开销。
[算法分析]
我们将
建边的时候除了题目中给定的边之外
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e5 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , ans , n;
struct node
{
int u , v , w;
}e[N];
int find ( int x )
{
if ( fa[x] == x ) return x;
else return fa[x] = find ( fa[x] );
}
int main()
{
n = read();
for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
for ( int i = 1 ; i <= n ; i ++ ) e[i].u = 0 , e[i].v = i , e[i].w = read();
int l = n;
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= n ; j ++ )
{
int temp = read();
if ( i < j ) e[++l] = { i , j , temp };
}
sort ( e + 1 , e + l + 1 , [](const node &a , const node &b) { return a.w < b.w; } );
for ( int i = 1 ; i <= l ; i ++ )
{
int fu = find(e[i].u) , fv = find(e[i].v);
if ( fu != fv )
{
fa[fu] = fv;
ans += e[i].w;
}
}
printf ( "%d" , ans );
return 0;
}
C. 【例题3】公路建设
[题目描述]
A国是一个新兴的国家,有
世界各地的大公司纷纷投资,并提出了自己的建设方案,他们的投资方案包括这些内容:公路连接的两座城市的编号,预计的总费用(假设他们的预计总是准确的)。
你作为A国公路规划局的总工程师,有权力决定每一个方案是否接受。但是政府给你的要求是:
-
要保证各个城市之间都有公路直接或间接相连。
-
因为是新兴国家,政府的经济实力还不强。政府希望负担最少的费用。
-
因为大公司并不是同时提出方案,政府希望每接到一个方案,就可以知道当前需要负担的最小费用和接受的投资方案,以便随时开工。关于你给投资公司的回复可以等到开工以后再给。
注意:A国一开始是没有公路的。
[输入格式]
第
第
[输出格式]
输出文件共有
[算法分析]
暴力跑
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e5 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , cnt , ans , n , m;
struct node
{
int u , v , w;
bool operator < ( const node &a ) const { return w < a.w; }
}e[N];
int find ( int x )
{
if ( fa[x] == x ) return x;
else return fa[x] = find ( fa[x] );
}
int main()
{
n = read() , m = read();
for ( int l = 1 ; l <= m ; l ++ )
{
e[l].u = read() , e[l].v = read() , e[l].w = read();
for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
for ( int i = l ; i ; i -- )//将新加入的这个值塞入到有序数列的对应位置中 用sort也行 但是更慢
{
if ( e[i].w < e[i-1].w ) swap ( e[i] , e[i-1] );
else break;
}
cnt = 0 , ans = 0;
for ( int i = 1 ; i <= l ; i ++ )
{
int fu = find(e[i].u) , fv = find(e[i].v);
if ( fu != fv )
{
fa[fu] = fv;
ans += e[i].w;
cnt ++;
}
if ( cnt == n - 1 ) break;
}
if ( cnt < n - 1 ) printf ( "0\n" );
else printf ( "%.1lf\n" , ans * 1.0 / 2 * 1.0 );
}
return 0;
}
D. 【例题4】构造完全图
[题目描述]
对于完全图
给你一棵树
[输入格式]
第一行正整数
接下来
保证输入数据构成一棵树。
[输出格式]
输出仅一个数,表示最小的完全图的边权和。
[算法分析]
类比最小生成树的原理 先将所有给定的边来排序 然后从小到大加入边 对于每一条边 将它两边集合之间需要连的边的边权都设置为
如果不排序 会导致出错 因为可能在以前加边的时候在这两个点之间加入了其他小边 不能保证两个点之间的最小边是这个大边
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e5 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , sz[N] , ans , n;
struct node
{
int u , v , w;
bool operator < ( const node &a ) const { return w < a.w; }
}e[N];
int find ( int x )
{
if ( fa[x] == x ) return x;
else return fa[x] = find ( fa[x] );
}
signed main()
{
n = read();
for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i , sz[i] = 1;
for ( int i = 1 ; i < n ; i ++ ) e[i].u = read() , e[i].v = read() , e[i].w = read();
sort ( e + 1 , e + n , [](const node a , const node b) { return a.w < b.w; } );
for ( int i = 1 ; i <= n - 1 ; i ++ )
{
int fu = find(e[i].u) , fv = find(e[i].v);
if ( fu != fv )
{
ans += ( sz[fu] * sz[fv] ) * ( e[i].w + 1 ) - 1;
sz[fu] += sz[fv];
fa[fv] = fu;
}
}
printf ( "%lld" , ans );
return 0;
}
E. 1.连接云朵
裸题
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e6 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , n , m , k;
struct node { int u , v , w; } e[N];
int find ( int x )
{
if ( fa[x] == x ) return x;
else return fa[x] = find(fa[x]);
}
signed main ()
{
n = read() , m = read() , k = read();
for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
for ( int i = 1 ; i <= m ; i ++ ) e[i].u = read() , e[i].v = read() , e[i].w = read();
sort ( e + 1 , e + m + 1 , [](const node &a , const node &b) { return a.w < b.w; } );
for ( int i = 1 , ans = n , res = 0 ; i <= m ; i ++ )
{
int fu = find(e[i].u) , fv = find(e[i].v);
if ( fu == fv ) continue;
fa[fu] = fv;
ans --;
res += e[i].w;
if ( ans == k ) { cout << res << endl; return 0; }
}
cout << "No Answer" << endl;
return 0;
}
F. 2.序列破解
[题目描述]
魔术师的桌子上有
花费
采取最优的询问策略,你至少需要花费多少元,才能保证猜出哪些杯子底下藏着球?
[输入格式]
第一行一个整数
第
其中
[输出格式]
输出一个整数,表示最少花费。
[算法分析]
神题 举例说明:
我们想得到3这个位置的奇偶性 那么有两种方法:
- 直接查询
- 查询
到 , 到 的值
所以当我们知道
考虑将
但是这条性质并不能搞出连通性 所以考虑换边的定义 知道
那么我们如果想要知道所有点的奇偶性 那么我们需要将所有相邻的点都相连 也就是说这张图是连通的 最后跑最小生成树即可
边数组需要开大一点()
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e6 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , n , m;
struct node { int u , v , w; } e[50000000];
int find ( int x )
{
if ( fa[x] == x ) return x;
else return fa[x] = find(fa[x]);
}
signed main ()
{
n = read();
for ( int i = 0 ; i <= n ; i ++ ) fa[i] = i;
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = i ; j <= n ; j ++ )
e[++m] = { i - 1 , j , read() };
sort ( e + 1 , e + m + 1 , [](const node &a , const node &b) { return a.w < b.w; } );
int ans = 0;
for ( int i = 1 ; i <= m ; i ++ )
{
int fu = find(e[i].u) , fv = find(e[i].v);
if ( fu == fv ) continue;
fa[fu] = fv;
ans += e[i].w;
}
cout << ans << endl;
return 0;
}
G. 3.生物进化
我们将所有可能的关系连边 跑最小生成树 将生成树中的边记录下来 再用dfs搜索一遍判断父亲
#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e7 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , n , tot;
int find ( int x )
{
if ( x == fa[x] ) return x;
return fa[x] = find(fa[x]);
}
int head[N] , cnt;
struct node { int to , nxt; } e[N];
void add ( int u , int v ) { e[++cnt] = { v , head[u] } , head[u] = cnt; }
struct edge { int u , v , w; } a[N];
void dfs ( int u , int ff )
{
fa[u] = ff;
for ( int i = head[u] ; i ; i = e[i].nxt )
if ( e[i].to != ff ) dfs ( e[i].to , u );
}
signed main ()
{
n = read();
for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 , w ; j <= n ; j ++ )
w = read() , a[++tot] = { i , j , w };
sort ( a + 1 , a + tot + 1 , [](const edge &a , const edge &b) { return a.w < b.w; } );
for ( int i = 1 ; i <= tot ; i ++ )
{
int fu = find(a[i].u) , fv = find(a[i].v);
if ( fu == fv ) continue;
fa[fu] = fv;
add ( a[i].u , a[i].v );
add ( a[i].v , a[i].u );
}
dfs ( 1 , 0 );
for ( int i = 2 ; i <= n ; i ++ ) cout << fa[i] << endl;
return 0;
}
H. 4.保留道路
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e7 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , n , m , WS , wg , tot , ans = inf;
struct node { int u , v , g , s; } e[N];
int find ( int x )
{
if ( x == fa[x] ) return x;
return fa[x] = find(fa[x]);
}
signed main ()
{
n = read() , m = read() , wg = read() , WS = read();
for ( int i = 1 ; i <= m ; i ++ ) e[++tot].u = read() , e[tot].v = read() , e[tot].g = read() , e[tot].s = read();
sort ( e + 1 , e + m + 1 , [](const node &a , const node &b) { return a.g < b.g; } );
for ( int i = 1 ; i <= m ; i ++ )
{
for ( int j = 1 ; j <= n ; j ++ ) fa[j] = j;
for ( int j = i ; j ; j -- )
{
if ( e[j].s < e[j-1].s ) swap ( e[j] , e[j-1] );
else break;
}
int maxs = -inf , maxg = -inf , ct = 0;
for ( int j = 1 ; j <= i ; j ++ )
{
int fu = find(e[j].u) , fv = find(e[j].v);
if ( fu == fv ) continue;
ct ++;
fa[fu] = fv;
maxs = max ( maxs , e[j].s );
maxg = max ( maxg , e[j].g );
if ( ct == n - 1 ) break;
}
if ( ct < n - 1 ) continue;
ans = min ( ans , wg * maxg + WS * maxs );
}
if ( ans == inf ) cout << "-1" << endl;
else cout << ans << endl;
return 0;
}
因此维护一个最小生成树边集 每一次只在
注意每次做最小生成树的时候需要同时更新边集
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e7 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , n , m , WS , wg , tot , ans = inf , top , j;
struct node { int u , v , g , s; } e[N] , sta[N];
int find ( int x )
{
if ( x == fa[x] ) return x;
return fa[x] = find(fa[x]);
}
signed main ()
{
n = read() , m = read() , wg = read() , WS = read();
for ( int i = 1 ; i <= m ; i ++ ) e[++tot].u = read() , e[tot].v = read() , e[tot].g = read() , e[tot].s = read();
sort ( e + 1 , e + m + 1 , [](const node &a , const node &b) { return a.g < b.g; } );
for ( int i = 1 ; i <= m ; i ++ )
{
for ( j = 1 ; j <= n ; j ++ ) fa[j] = j;
sta[++top] = e[i];
for ( j = top ; j ; j -- )
{
if ( sta[j-1].s > sta[j].s ) swap ( sta[j] , sta[j-1] );
else break;
}
int ct = 0;
for ( j = 1 ; j <= top ; j ++ )
{
int fu = find(sta[j].u) , fv = find(sta[j].v);
if ( fu == fv ) continue;
fa[fu] = fv;
sta[++ct] = sta[j];
if ( ct == n - 1 ) break;
}
if ( ct == n - 1 ) ans = min ( ans , wg * e[i].g + WS * sta[ct].s );
top = ct;
}
if ( ans == inf ) cout << "-1" << endl;
else cout << ans << endl;
return 0;
}
I. 5.最小距离和
方法1:循环两次 对每个点两两建边 边权为两点距离 最后做最小生成树 显然会爆炸
方法2:可以发现 方法一建了很多无用的边 我们考虑简化
如果是一维空间的话 那么只有相邻的两个点可以被连边 因为如果不相邻的两个点连边的话 显然不如相邻连边优
那么推广到三维 将边分别按照xyz坐标排序一次 每次将相邻的点连边即可
#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e7 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int fa[N] , n , cnt , ans;
struct node { int x , y , z , id; } a[N];
struct edge { int u , v , w; } e[N];
int find ( int x )
{
if ( x == fa[x] ) return x;
else return fa[x] = find(fa[x]);
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].x = read() , a[i].y = read() , a[i].z = read() , a[i].id = i;
for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
sort ( a + 1 , a + n + 1 , [](const node a , const node b) { return a.x < b.x; } );
for ( int i = 1 ; i < n ; i ++ ) e[++cnt] = { a[i].id , a[i+1].id , a[i+1].x - a[i].x };
sort ( a + 1 , a + n + 1 , [](const node a , const node b) { return a.y < b.y; } );
for ( int i = 1 ; i < n ; i ++ ) e[++cnt] = { a[i].id , a[i+1].id , a[i+1].y - a[i].y };
sort ( a + 1 , a + n + 1 , [](const node a , const node b) { return a.z < b.z; } );
for ( int i = 1 ; i < n ; i ++ ) e[++cnt] = { a[i].id , a[i+1].id , a[i+1].z - a[i].z };
sort ( e + 1 , e + cnt + 1 , [](const edge a , const edge b) { return a.w < b.w; } );
for ( int i = 1 ; i <= cnt ; i ++ )
{
int fu = find(e[i].u) , fv = find(e[i].v);
if ( fu == fv ) continue;
fa[fu] = fv; ans += e[i].w;
}
cout << ans << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!