最小生成树合集(讲解与例题)

概念:

无向图中,其某个子图中任意两个顶点都互相连通并且是一棵树,称之为生成树;若每个顶点有权值,则其权值和最小的生成树为最小生成树。

适用范围:

能用图表示,求其权值和最小(或次小等)。

算法:

(1) prim算法

参考自白皮p105.
类比Dijkstra算法,从某个顶点出发,不断加边。

int vis[MAXN];  //标记数组,表该点是否已经在集合中
int cost[MAXN][MAXN];   //图,下标从0开始 ,到n-1 
int lw[MAXN];   // 最短路径 
int  prim(int n)
{
	int ans=0;
	memset(vis,0,sizeof(vis));
	vis[0]=1;
	for(int i=1;i<n;i++){
		int mi=INF,p=-1;
		for(int j=0;j<n;j++)
		   if(!vis[j]&&mi>lw[j])
		   {
		   	  p=j;
		   	  mi=lw[j];
		   }
		if(mi==INF)  return -1  //原图不连通
		ans+=mi;
		for(int j=0;j<n;j++)
		  if(!vis[j]&&lw[j]>cost[p][j]) 
		     lw[j]=cost[p][j];
	}
	return ans;
}

(2) Kruskal算法

将所有边的权值排序,用并查集判读是否连通,不连通则加边。

struct edge
{
	int u,v,cost;
}x[MAXN;
int fa[MAXN]; 
int find(int a)
{
	return fa[a]==-1?a:fa[a]=find(fa[a]);
}
bool cmp(struct edge a,struct edge b)
{
	return a.cost<b.cost;
}
void unite(int a,int b)
{
	int f=find(a),fb=find(b);
	if(f!=fb)
	  fa[fb]=f;
}
bool same(int a,int b)
{
	return find(a)==find(b);
}
int kruskal()
{
	int ans=0;
	ini(); //初始化
	sort(x,x+sum,cmp);
	for(int i=0;i<n;i++)
	{
		if(!same(x[i].u,x[i].v))
		{
			unite(x[i].u,x[i].v);
			ans+=x[i].cost;
		}
	}
	return ans;
} 

例题:

(1)poj-1251
最小生成树基本模板题

struct stu
{
	int u;
	int v;
	int cost;
}edge[2500];
int n,fa[100]; 
char ch[3],s[3];
void ini()
{
	for(int i=0;i<=70;i++)
	   fa[i]=i;
}
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unite(int a,int b)
{
	int x=find(a),y=find(b);
	if(x!=y)    fa[y]=x;
}
bool same(int a,int b)
{
	return find(a)==find(b);
}
bool cmp(stu a,stu b)
{
	return a.cost<b.cost;
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		int m,sum=0,ans=0;
		memset(edge,0,sizeof(edge));
		for(int j=0;j<n-1;j++){
			scanf("%s%d",s,&m);
			for(int k=0;k<m;k++){
			scanf("%s%d",ch,&edge[sum].cost);
			edge[sum].u=s[0]-'A';
			edge[sum].v=ch[0]-'A';
			//cout<<x[sum].u<<" "<<x[sum].v<<" "<<x[sum].cost<<endl; 
			sum++;
		    }
		}
		ini();
		sort(edge,edge+sum,cmp);
		for(int i=0;i<sum;i++){
			stu t=edge[i];
			if(!same(t.u,t.v))
			{
				unite(t.u,t.v);
				ans+=t.cost;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

(2) 畅通工程再续
根据题干,最多一百个点,所以最多100*100/2条边,可以直接建边,就变成了最小生成树模板题。
注意分析数据范围!

注意点的数目和构造边,判读数据大小 
struct stu
{
	int u;
	int v;
	double cost;
}edge[21000];
int x[110],y[110],fa[11000],c;
bool vj(int i,int j)
{
	int t=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
	if(t>=100&t<=1000000)
	  return true;
	else
	  return false;
}
bool cmp(stu a,stu b)
{
	return a.cost<b.cost;
}
int find(int a)
{
	return fa[a]==-1?a:fa[a]=find(fa[a]);
}
bool same(int a,int b)
{
	return find(a)!=find(b);
}
void unite(int a,int b)
{
	int f=find(a),f1=find(b);
	if(f!=f1)
	   fa[f1]=f;
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		memset(edge,0,sizeof(edge));
		scanf("%d",&c);
		memset(fa,-1,sizeof(fa));
		for(int i=0;i<c;i++){
			scanf("%d%d",&x[i],&y[i]);
		}
		int sum=0;
		for(int i=0;i<c-1;i++){
			for(int j=i+1;j<c;j++){
				if(vj(i,j))
				{
					edge[sum].u=i;edge[sum].v=j;
					edge[sum].cost=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
					//cout<<edge[sum].u<<" "<<edge[sum].v<<" "<<edge[sum].cost<<endl;
					sum++;
				}
			}
		}
		if(sum==0)
		{
			printf("oh!\n");
			continue;
		}
		double ans=0;
		sort(edge,edge+sum,cmp);
		int cnt=0;
		for(int i=0;i<sum;i++){
			if(same(edge[i].u,edge[i].v))
			{
				ans+=edge[i].cost;
				unite(edge[i].u,edge[i].v);
				cnt++;
			}
		}
		if(cnt!=c-1)
		  printf("oh!\n");
		else
		{
			ans*=100;
			printf("%.1lf\n",ans);
		}
	}
	return 0;
}

(3) Building a Space Station
需要先读懂题意。。。题意是给你一个球的球心坐标和半径r,两个球相交或相切则距离为零,否则为球心距离减两个球的半径。然后又变成了最小生成树模板题。

struct stu
{
	double x,y,z,r;
}pos[11000];
struct edg
{
	double u,v,cost;
}edge[11000];
int fa[11000];
bool check(int a,int b)
{
	return pos[a].r+pos[b].r>=sqrt((pos[a].x-pos[b].x)*(pos[a].x-pos[b].x)+(pos[a].y-pos[b].y)*(pos[a].y-pos[b].y)+(pos[a].z-pos[b].z)*(pos[a].z-pos[b].z));
}
bool cmp(struct edg a,struct edg b)
{
	return a.cost<b.cost;
}
int find(int a)
{
	return fa[a]==-1?a:fa[a]=find(fa[a]);
}
void unite(int a,int b)
{
	int x=find(a),y=find(b);
	if(x!=y)
	  fa[y]=x;
}
bool same(int a,int b)
{
	return find(a)==find(b);
}
int main()
{
	int n;
	while(scanf("%d",&n)&&n)
	{
		memset(fa,-1,sizeof(fa));
		for(int i=0;i<n;i++)
		   scanf("%lf%lf%lf%lf",&pos[i].x,&pos[i].y,&pos[i].z,&pos[i].r);
		int sum=0;
		for(int i=0;i<n;i++){
			for(int j=i+1;j<n;j++){
				if(check(i,j))
				{
					edge[sum].u=i;
					edge[sum].v=j;
					edge[sum].cost=0;
					sum++;
				}
				else
				{
					edge[sum].u=i;
					edge[sum].v=j;
					edge[sum].cost=sqrt((pos[i].x-pos[j].x)*(pos[i].x-pos[j].x)+(pos[i].y-pos[j].y)*(pos[i].y-pos[j].y)+(pos[i].z-pos[j].z)*(pos[i].z-pos[j].z))-pos[i].r-pos[j].r;
					sum++;
				}
			}
		}
		double ans=0;
		sort(edge,edge+sum,cmp);
		for(int i=0;i<sum;i++){
			if(!same(edge[i].u,edge[i].v))
			{
				unite(edge[i].u,edge[i].v);
				ans+=edge[i].cost;
			}
		}
		printf("%.3lf\n",ans);
	}
	return 0;
}

(4)Constructing Roads
修路,求最少花费,模板中的模板,之后的q组输入表示a和b之间已经有路,将其权值改成0就行。

struct stu
{
	int u,v,cost;
}x[11000];
int fa[11000]; 
int find(int a)
{
	return fa[a]==-1?a:fa[a]=find(fa[a]);
}
bool cmp(struct stu a,struct stu b)
{
	return a.cost<b.cost;
}
void unite(int a,int b)
{
	int f=find(a),fb=find(b);
	if(f!=fb)
	  fa[fb]=f;
}
bool same(int a,int b)
{
	return find(a)==find(b);
}
int map1[1100][1100],n,t;
int main()
{
	cin>>n;
	memset(fa,-1,sizeof(fa));
	memset(x,-1,sizeof(x));
	memset(map1,-1,sizeof(map1));
	for(int i=0;i<n;i++)
	  for(int j=0;j<n;j++)
	    scanf("%d",&map1[i][j]);
	cin>>t;
	for(int i=0;i<t;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		map1[a-1][b-1]=map1[b-1][a-1]=0;
	}
	int sum=0;
	for(int i=0;i<n;i++)
	  for(int j=0;j<n;j++)
	    if(i!=j)
	    {
	    	x[sum].u=i;
	    	x[sum].v=j;
	    	x[sum].cost=map1[i][j];
	    	sum++;
	    }
	long long ans=0;
	sort(x,x+sum,cmp);
	for(int i=0;i<sum;i++){
		if(!same(x[i].u,x[i].v))
		{
			unite(x[i].u,x[i].v);
			ans+=(long long)x[i].cost;
		}
	}
	cout<<ans<<endl;
	return 0;
}

(5) Truck History
题目很长不好理解。。其实就是给你n个字符串,用一个7位的字符串代表一个编号,两个编号之间的distance代表这两个编号之间不同字母的个数。一个编号只能由另一个编号“衍生”出来,代价是这两个编号之间相应的distance,现在要找出一个“衍生”方案,使得总代价最小,也就是distance之和最小。
牵扯到了总distance最小,每个distance又是表示两个编号之间的,可以往建图方向考虑,自然也就想到了最小生成树。

string ch[3100];
int n;
//int map[3100][3100];
struct stu
{
	int u,v,cost;
}x[2000000];  //注意范围2000*2000会爆 
int sum,fa[2000000],ans;
bool cmp(struct stu a,struct stu b)
{
	return a.cost<b.cost;
}
int find(int a)
{
	return fa[a]==-1?a:fa[a]=find(fa[a]);
}
bool same(int a,int b)
{
	return find(a)==find(b);
}
void unite(int a,int b)
{
	int f=find(a),fb=find(b);
	if(f!=fb)
	  fa[fb]=f;
}
int main()
{
	while(scanf("%d",&n)&&n)
	{
		sum=ans=0;
		memset(fa,-1,sizeof(fa));
		memset(x,0,sizeof(x));
		for(int i=0;i<n;i++)
		   cin>>ch[i];
		for(int i=0;i<n;i++){
			for(int j=i;j<n;j++){
				int now=0;
				for(int k=0;k<7;k++){
					if(ch[i][k]!=ch[j][k])
					{
						now++;
					}
				}
				x[sum].u=i;
				x[sum].v=j;
				x[sum].cost=now;
				sum++;
			}
		}
		sort(x,x+sum,cmp);
		for(int i=0;i<sum;i++){
			if(!same(x[i].u,x[i].v))
			{
				unite(x[i].u,x[i].v);
				ans+=x[i].cost;
			}
		}
		printf("The highest possible quality is 1/%d.\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值