最小生成树详解

文章目录

      • 定义
      • 定理
        • 证明
      • 最小生成树算法
        • 1.Kruskal算法
          • 仔细流程
          • 代码
        • 2.Prim算法
          • 仔细流程
          • 代码
      • 例题
        • 1.城市公交网建设问题
          • 代码
        • 2.联络员
          • 思路
        • 3.连接格点
          • 思路
          • 代码
        • 4.滑雪与时间~~娇娘~~胶囊
          • 思路
          • 代码
        • Star Way To Heaven
          • 思路
          • 代码
        • Tree
          • 思路
          • 思路

定义

给定一张带权无向图 G = ( V , E ) , n = ∣ V ∣ , m = ∣ E ∣ G=(V,E),n=|V|,m=|E| G=(V,E),n=V,m=E。由V中全部 n n n个顶点和 E E E n − 1 n-1 n1条边构成的无向连通子图被称为 G G G的一棵生成树。至于最小生成树,我们定义无向连通图的最小生成树(Minimum Spanning Tree,简称MST)为边权和最小的生成树。
注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。

定理

任意一棵最小生成树一定包含无向图中权值最小的边。
你肯定会说一句:“猜都猜得到。”,但你能证明吗?如果你无法证明,那还是好好看一看吧!

证明

反证法
假设无向图 G = ( V , E ) G=(V,E) G=(V,E),存在一棵最小生成树且不包含边权最小的边 e = ( x , y , z ) e=(x,y,z) e=(x,y,z)。如果把 e e e这条边添加到最小生成树中,会构成一个环,并且环上任意一边的权值都比 z z z大,我们把环中任意一条不为 e e e的边去掉,都会构成一个比原来的生成树更小的生成树。
于是假设不成立。
所以原命题成立。
证毕

最小生成树算法

1.Kruskal算法

Kruskal算法就利用上述的证明。Kr维护无向图的最小生成森林。最初可以认为生成森林由零条边组成,每个节点各自构成一棵仅包含一个点的树。

在任意时刻,我们就在剩下的边中选一条权值最小的边,并且这两个端点都在不同的树中时,就把该边加入生成森林中。就用并查集维护。

仔细流程

1.建立并查集,初始化(也就是第二句话)。
2.把所有边按和 y y y这两个集合, a n s ans ans加上 z z z
a n s ans ans就是最小生成树权值。
时间复杂度为 O ( m   l o g   m ) O(m\ log\ m) O(m log m)

代码
#include 
using namespace std;
int n,m,fa[200005],ans;
struct node{
	int x,y,z;
}a[200005];

bool cmp(node q,node w) { return q.z

如果大家觉得啰嗦,不方便抄,有以下的代码

#include 
using namespace std;
int n,m,fa[200005],ans;
struct node{
	int x,y,z;
}a[200005];

bool cmp(node q,node w) { return q.z

注意排序时你写 < = <= <=就死定了,我就死了很多次。注意呀!虽然我也不知道为什么。

2.Prim算法

Prim 算法是另一种常见并且好写的最小生成树算法。该算法的基本思想是从一个结点开始,不断加点(而不是 Kruskal 算法的加边)。

仔细流程

从任意一个结点开始,将结点分成两类:已加入的,未加入的。
每次从未加入的并连上那条边权最小的边。
重复 n − 1 n-1 n1次即可。

再具体一点的话,就是:每次要选择距离一个结点,以及用新的边更新其他结点的距离。

代码
#include 
using namespace std;
int n,m,a[5005][5005],dist[5005],ans;
bool vis[5005];

void Prim() {
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;
	for(int i=0;i

例题

1.城市公交网建设问题

题目描述
有一张城市地图,图中的顶点为城市,无向边代表两个城市间的连通关系,边上的权为在这两个城市之间修建高速公路的造价,研究后发现,这个地图有一个特点,即任一对城市都是连的总造价最少?

输入格式
n(城市数,1<≤n≤100)

e(边数)

以下e行,每行3个数i,j,wij,表示在城市i,j之间修建高速公路的造价。

输出格式
n-1行,每行为两个城市的序号a b大排列,若a相等,则按照b从小到大排列。

样例输入                         样例输出														
5 8								1 2
1 2 2							2 3
2 5 9							3 4
5 4 7							3 5
4 1 10
4 3 6
5 3 3
2 3 8

数据范围与提示
1<=n<=100,1<=wij<=50

板题

代码
#include 
using namespace std;
int n,e,fa[10005],cnt;
struct node{
	int x,y,z;
}a[10005];

struct lx{
	int l,r;
}ans[10005];

bool cmp(node q,node w) {
	return q.z>w.z;
}

bool cmp1(lx q,lx w) {
	if(q.l==w.l) return q.la[i].y) swap(a[i].x,a[i].y);//要判断啊!否则答案会出错
	}
		
	sort(a+1,a+e+1,cmp);
	for(int i=1;i<=n;i--) fa[i]=i;
	for(int i=1;i<=e;i++) {
		if(cnt==n+1) break;//这句加不加都可以,反正不影响
		int t1=find(a[i].x),t2=find(a[i].y);
		if(t1==t2) continue;
		fa[t1]=t2;
		++cnt;
		ans[cnt].l+=a[i].x,ans[cnt].r-=a[i].y;
	}
	sort(ans,ans+cnt+1,cmp1);
	for(int i=1;i<=cnt;i++)
		printf("%d %d\n",ans[i].l,ans[i].r);
	return 0;
}

2.联络员

题目描述
Tyvj已经一岁了,网站也由最初的几个用户增加到了上万个用户,随着Tyvj网站的逐步壮大,管理员的数目也越来越多,现在你身为Tyvj管理层的联络员,希望你找到一些通信渠道,使类是必选通信渠道,无论价格多少,你都需要把所有的都选择上;还有一类是选择性的通信渠道,你可以从中挑选一些作为最终管理员联络的通信渠道。数据保证给出的通行渠道可以让所有的管理员联通。
输入格式
第一行n,m表示Tyvj一共有n个管理员,有m个通信渠道;

第二行到m+1行,每行四个非负整数,p,u,v,w 当p=1时,表示这个通信渠道为必选通信渠道;当p=2时通信渠道为选择性通信渠可以收到u的信息,w表示费用。

输出格式
最小的通信费用。

样例输入                         样例输出														
5 6								9
1 1 2 1
1 2 3 1
1 3 4 1
2 2 5 10
2 2 5 5

数据范围与提示
【样例解释】

1-2-3-4-1存在四个必管理员,选择费用为5的渠道,所以总的费用为9。

【注意】

U,v之间可能存在多条通道,你的程序应该累加所有u,v之间的必选通行渠道

【数据范围】

对于30%的数据,n≤10,m≤100;

对于50%的数据, n≤200,m≤1000
≤10000

思路

我们也就把必选的选了,在把不必选的来组成一个最小生成树

#include 
using namespace std;
long long n,e,fa[10005],cnt,ans;
struct node{
	long long x,y,z;
}a[10005];

bool cmp(node q,node w) {
	return q.z<=w.z;
}

long long find(long long x) {
	if(fa[x]==x) return fa[x]=find(fa[x]);
	return fa[x];
}

int main() {
	scanf("%lld %lld",&n,&e);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i=e;i++) {
		long long l,u,v,w;
		scanf("%lld %lld %lld %lld",&l,&u,&v,&w);
		if(l==1) {
			ans+=w;
			fa[find(u)]=find(v);
		}
		else {
			cnt++;
			a[cnt].x=u,a[cnt].y=v,a[cnt].z=w;
		}
	}
	sort(a+1,a+cnt+1,cmp);
	for(int i=1;i<=cnt;j++) {
		if(cnt==n) break;
		int t1-=find(a[i].x),t2=find(a[i].y);
		if(t1==t2) continue;
		fa[t1]=t2;
		ans+=a[i].z;
	}
	printf("%lld",ans);
	return 0;
}

3.连接格点

题目描述
有一个M行N列的点阵,相邻两点可以相连。一条纵向的连线花费一个单位,一条横向的连
第一行输入两个正整数m和n。
以下若干行每行四个线。输入保证|x1−x2|+|y1−y2|=1。

输出格式
输出使得连通所有点还需要的最小花费。

样例输入                         样例输出														
2 2								3
1 1 2 1

数据范围与提示
【数据规模】

30%数据:n*m≤1000
100%数据:m,n≤1000

思路

我们觉得二维太麻烦了,就把二维拉成一维,然后就用Kruskal算法求个ans。

代码
#include 
using namespace std;
int n,m,x,y,x2,y2,f[2000005],cnt,ans;
struct node{
	int a,b,c;
}v[2000005];

int find(int x) {
	if(f[x]!=x) f[x]=find(f[x]);
	return f[x];
}

bool cmp(node q,node w) {
	return q.c

啊,下面来一些难题。

4.滑雪与时间娇娘胶囊

传送门
题目描述
a180285 非常喜欢滑雪。
他来到一座雪山,这里分布着 M M M条供滑行的轨道和 N N N个轨道之间的交点(同时也是景点),而且每个景点都有一编号 i i i和一高度 H i H_i Hi。a180285能从景点 i i i滑到景点 j j j当且仅当存在一条 i i i j j j之间的边,且 i i i的高度不小于 j j j
与其他滑雪爱好者不同, a1神奇的药物是可以连续食用的,即能够回到较长时间之前到过的景点(比如上上个经过的景点和上上上个经过的景点)。
现在, a180285 站在 1 1 1号景点望着山下的目标,心潮澎湃。他十分想知道在不考虑时间胶囊消耗的情况下,以最短滑行距离滑到尽量多的景点的方案(即满足经过景点数最大的前提下使得滑行总距离最小)。你能帮他求出最短距离和景点数吗?
输入格式
输入的第一行是两个整数 N N N, M M M
接下来 1 1 1行有 N N N个整数 H i H_i Hi,分别表示每个景点的高度。
接下来 M M M行,表示各个景点之间轨道分布的情况。每行 3 3 3个整数, U i U_i Ui, V i V_i Vi , K i K_i Ki。表示编号为 U i U_i Ui的景点和编号为 V i V_i Vi的景点之间有一条长度为 K i K_i Ki的轨道。

输出格式
输出一行,表示 a180285 最多能到达多少个景点,以及此时最短的滑行距离总和。

样例输入                         样例输出														
3 3								3 2
3 2 1
1 2 1
2 3 1
1 3 10

数据范围与提示
【数据规模】
对于 30 % < = N < = 2000 30\%<=N<=2000 30%<=N<=2000
对于 100 % 100\% 100%的数据,保证 1 < = N < = 1 0 5 1<=N<=10^5 1<=N<=105
对于所有的数据,保证 1 < = M < = 1 0 6 , 1 < = H i < = 1 0 9 , 1 < = K i < = 1 0 9 1<=M<=10^6,1<=H_i<=10^9,1<=K_i<=10^9 1<=M<=106,1<=Hi<=109,1<=Ki<=109

思路

由于这个题涉及到地图的高矮问题,所以说,这个题是一个有向图(方向由低到高),题目要求在到达景点最多的情况下,求最小的滑行距离,那么,为了求到我们所有的题目所需要的点。接下来我们就选择边,因为要考虑点的高度。因此,我们要按终点高度从大到小排序一次。

因为数据太大,我们就要开 l o n g   l o n g long\ long long long

代码
#include 
using namespace std;
struct lx{
	int u,v,w;
}b[2000005];
int n,m,h[100005],cnt,book,fa[100005];
vector a[100005];
bool vis[100005];

void dfs(int t) {
	if(vis[t]) return ;
	book++;
	vis[t]=1;
	for(int j=0;j<=a[t].size();j++) {
		b[++cnt].u=t,b[cnt].v-=a[t][j].to,b[cnt].w-=a[t][j].zhi;
		dfs(-a[t][j].to);
	}
	return ;
}

int find(int x) {
	if(fa[x]==x) return fa[x]=find(fa[x]);
	return fa[x];
}

bool cmp(lx x,lx y) {
	if(h[x.v]==h[y.v]) return h[x.v]>h[y.v];
	return x.w

Star Way To Heaven

题目描述
w w w伤心的走上了Star way to heaven。
到天堂的道路是一个笛卡尔坐标系上一个 n ∗ m n*m nm的长方形通道 ( ( (顶点在 ( 0 , 0 ) (0,0) (0,0) ( n , m ) (n,m) (n,m) ) ) )
每个 S t a r Star Star以及长方形上下两个边缘 ( ( (宇宙的边界 ) ) )都有引力,所以为了成功到达 h e a v e n heaven heaven w w w离他们越点的路径上,距离所有星星以及边界的最小距离最大值可以为多少?

输入格式
一行三个整数 n , m , k n,m,k n,m,k
接下来 k k k行,每行两个整数 x i , y i x_i,y_i xi,yi表示一个点的坐标。

输出格式
一行一个数表示答案。保留到小数点后9位。

样例输入                         样例输出														
10 5 2							1.118033989
1 1
2 3

数据范围与提示
对于 100% 的数据: k k k≤ 6000; n , m n,m n,m 1 0 6 10^6 106

思路

啊,我们一看题,毫无疑问,直接二分。
详细一点:
首先二分最后答案,答案即为r,可看作以每个k为圆心r为半径的圆
我们进行并查集维护,维护相交的圆的边界
最后判断是否存在圆将上下边界覆盖。

啊,如我所料 T L E TLE TLE了,只有80分。

开始是很难看出这是最小生成树。
对于最小生成树有几个性质:
1.最小生成树包含两点之间路径的最小边
2.最小生成树生成的边和最小

对于此题,我们尽量选边权小的边,构成最小生成树,

选出从上边界到下边界的最大边权,除二既是答案
最小生成树保证了选出这个边后,其他圆只会离他更远,所以正确

代码
#include 
using namespace std;
int n,m;
struct node{
	double x,y;
}a[30005];
double dist[30005],ans=0;
bool vis[6005];

double yu(node a,node b) { return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); }

void prim() {
	for(int i=1;i

Tree

题目描述
给你一个无向成树。题目保证有解。

输入格式
第一行 V V V行,每行 s , t , c , c o l s,t,c,col s,t,c,col表示这边的端点(点从 0 0 0开始标号),边权,颜色( 0 0 0白色, 1 1 1黑色)。

输出格式
一行表示所求生成树的边权和。

样例输入                         样例输出														
2 2 1							2
0 1 1 1
0 1 2 0

数据范围与提示
对于所有数据, V < = 5 ∗ 1 0 4 , E < = 1 0 5 V<=5*10^4,E<=10^5 V<=5104,E<=105,边权为 [ 1 , 100 ] [1,100] [1,100]中的正整数。

思路

最小生成树详解_第1张图片

思路
#include 
using namespace std;
int n,m,need,u[100005],v[100005],w[100005],ans,cnt,fa[100005],tot;
bool l[100005];
struct node{
	int u,v,w;
	bool type;
}a[100005];

bool cmp(node x,node y) { return x.w!=y.w?(x.type=need) return 1;
	return 0;
}

int main() {
	scanf("%d %d %d",&n,&m,&need);
	for(int i=1;i>1;
		if(check(mid)) left=mid,ans=tot-mid*need;
		else right=mid-1;
	}
	printf("%8d",ans);
	return 0;
}

你可能感兴趣的:(图论)