把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

最小生成树

最小生成树

给定一个无向图,如果它的某个子图中任意两个顶点都互相连通且是一棵树,那么这棵树就是生成树 其中,边权和最小的生成树就是最小生成树(MST)

一些性质
n-1条边

若T1,T2都是最小生成树,则T1,T2的各边权是相同的(可能连接的边不同),换句话说,若边权各不相同,则最小生成树唯一(实际上也是拟阵贪心的性质)

往生成树上加一条边就形成一个环,假设是(u,v)加边,那么就形成(u,…,lca(u,v),…,v,u)这个环

基本算法

Prim

与Dijkstra比较像 Dijkstra是什么(大雾
我们先在某一个集合中搞一个点
定义d[i]表示点i到这个集合的最小距离
然后不断往这个集合里添加离这个集合最近的点,直到所有的点都加入了这个集合。
每一次加入一个点 都要去搞一下这个点的相邻的点 来更新d[i]

感觉一下正确性显然(什么鬼操作
证明 略~~(其实讨厌这个字~~
详见白书106页

int prim()
{
	d[0]=0;
	int ans=0;
	while(1)
	{
		int u=-1;
		for(int v=0;v<=n;v++)
			if(!vis[v]&&(u==-1||d[v]<d[u]))
				u=v;
		if(u==-1) break;
		vis[u]=1;
		ans+=d[u];
		for(int v=0;v<=n;v++)
			d[v]=min(d[v],c[u][v]);
	}
	return ans;
}

同Dijkstra一样,Prim也可以用堆优化 mark一下
但是我感觉优化效果不明显(可能要在一些特殊数据下或者数据较大的时候吧

Kruskal

按照边的权值从小到大依次check一下,如果不产生环,就把这条边加进去,直到联通所有的点(根据树的性质,则为有n-1条边时
判断是否产生环,主要是看这条边的两个端点是否已经连通
就需要并查集来维护

void Init()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
}
int Find(int x)
{
	if(f[x]!=x)
		return f[x]=Find(f[x]);
	return f[x];
}
bool Union(int x,int y)
{
	int u=Find(x),v=Find(y);
	if(u==v) return 0;
	if(u>v) f[u]=v;
	else f[v]=u;
	return 1;
}
int Kruskal()
{
	Init();
	int num=0,ans=0;
	for(int i=1;i<=m;i++)
		if(Union(edge[i].u,edge[i].v))
		{
			num++;
			ans+=edge[i].w;
			if(num==n-1) break;
		}
	if(num!=n-1) return -1;//没有生成树 
	return ans;
}

考虑到边一般是u,v,w三元组,交换两个结构体的时间开销比较大,所以我们也可以对下标搞排序,就只用交换1个变量就可以啦

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 110
#define MAXM 5010
struct node{
	int u,v,w;
}edge[MAXM];
int n,m;
int f[MAXN],order[MAXM];
void st()
{
	for(int i=1;i<=m-1;i++)
		for(int j=1;j<=m-i-1;j++)
			if(edge[order[j]].w>edge[order[j+1]].w)
			{
				int temp=order[j];
				order[j]=order[j+1];
				order[j+1]=temp;
			}
		
}
void Init()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
}
int Find(int x)
{
	if(f[x]!=x)
		return f[x]=Find(f[x]);
	return f[x];
}
bool Union(int x,int y)
{
	int u=Find(x),v=Find(y);
	if(u==v) return 0;
	if(u>v) f[u]=v;
	else f[v]=u;
	return 1;
}
int Kruskal()
{
	Init();
	int num=0,ans=0;
	for(int i=1;i<=m;i++)
		if(Union(edge[order[i]].u,edge[order[i]].v))
		{
			num++;
			ans+=edge[order[i]].w;
			if(num==n-1) break;
		}
	return ans;
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		m=0;
		for(int i=1;i<=n*(n-1)/2;i++)
		{
			int uu,vv,ww;
			scanf("%d %d %d",&uu,&vv,&ww);
			edge[++m].u=uu,edge[m].v=vv,edge[m].w=ww,order[m]=i;
		}
		st();
		printf("%d\n",Kruskal());
	}
}//这个代码好像没有调出来qwq

Prim VS Kruskal

我感觉更喜欢Kruskal一点
也不知道为什么 它看起来比较快一点

(这两个都是打的单纯的未优化版的板子)

总的来说
他们都是基于贪心的思想
Prim与点的关系比较大,适用于稠密图
Kruskal与边的关系比较大,适用于稀疏图

拟阵

这个要展开的话就非常冗杂了
所以大概简单说一下
另外 更深入了解的话 mark一下 https://blog.csdn.net/qingyingliu/article/details/82055737

在这里插入图片描述

必选某些边的最小生成树,就可以把那些必选的边先搞进去,再开始算法。

例题

先上板子

HDU 1233 还是畅通工程

某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。

测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。

对每个测试用例,在1行里输出最小的公路总长度。
Sample Input
3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0
Sample Output
3
5

Prim

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 2005
#define INF 0x3f3f3f3f
int c[MAXN][MAXN],d[MAXN];
bool vis[MAXN];
int n,m;
int prim()
{
	d[1]=0;
	int ans=0;
	while(1)
	{
		int u=-1;
		for(int v=1;v<=n;v++)
			if(!vis[v]&&(u==-1||d[v]<d[u]))
				u=v;
		if(u==-1) break;
		vis[u]=1;
		ans+=d[u];
		for(int v=1;v<=n;v++)
			d[v]=min(d[v],c[u][v]);
	}
	return ans;
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		memset(d,INF,sizeof(d));
		memset(c,INF,sizeof(c));
		memset(vis,0,sizeof(vis));
		m=0;
		for(int i=1;i<=n*(n-1)/2;i++)
		{
			int uu,vv,ww;
			scanf("%d %d %d",&uu,&vv,&ww);
			c[uu][vv]=c[vv][uu]=ww;
		}
		printf("%d\n",prim());
	}

	return 0;
}

Kruskal

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 110
#define MAXM 5010
struct node{
	int u,v,w;
}edge[MAXM];
int n,m;
int f[MAXN];
bool cmp(node p,node q)
{
	return p.w<q.w;
}
void Init()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
}
int Find(int x)
{
	if(f[x]!=x)
		return f[x]=Find(f[x]);
	return f[x];
}
bool Union(int x,int y)
{
	int u=Find(x),v=Find(y);
	if(u==v) return 0;
	if(u>v) f[u]=v;
	else f[v]=u;
	return 1;
}
int Kruskal()
{
	Init();
	int num=0,ans=0;
	for(int i=1;i<=m;i++)
		if(Union(edge[i].u,edge[i].v))
		{
			num++;
			ans+=edge[i].w;
			if(num==n-1) break;
		}
	return ans;
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		m=0;
		for(int i=1;i<=n*(n-1)/2;i++)
		{
			int uu,vv,ww;
			scanf("%d %d %d",&uu,&vv,&ww);
			edge[++m].u=uu,edge[m].v=vv,edge[m].w=ww;
		}
		sort(edge+1,edge+m+1,cmp);
		printf("%d\n",Kruskal());
	}
}

建图比较巧妙的题目

BZOJ 1601
BZOJ 3714

比较暴力的方式求解生成树问题的变式

数据范围较小
一般枚举一个什么东西(比方说 生成树上的边,所有边,某一权值

校内OJ 1362
UVAlive 6837
BZOJ 3754

生成树的性质相关

次小生成树
校内OJ 2032
BZOJ 1016

与二分相关

校内OJ 2138
POJ 2728
posted @ 2019-01-29 20:53  Starlight_Glimmer  阅读(7)  评论(0编辑  收藏  举报  来源
浏览器标题切换
浏览器标题切换end