最小生成树的扩展应用

1144. 连接格点
思路:最小生成树kruskal。这道题有几个特别的地方。①二维转一维,将二维坐标转换为一维来表示,才方便把点之间用并查集连接起来。②存边,这道题肯定是要先把纵向的边连接起来再连接横向的,所以我们在存边的时候直接先把纵向的边存起来,再存横向的边,这样就可以免去排序了。

#include<iostream>
using namespace std;
const int N=1010,M=N*N,K=2*N*N;
int n,m,k;
int ids[N][N];
struct edge
{
	int a,b,w;
}e[K];
int p[M];
int find(int x)
{
	if(x!=p[x])
		p[x]=find(p[x]);
	return p[x];
}
void get_edges()
{
	int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1},dw[4]={1,2,1,2};
	for(int z=0;z<2;z++)
	{
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				for(int u=0;u<4;u++)//先把纵边存起来就不用排序了
					if(u%2==z)
					{
						int x=i+dx[u],y=j+dy[u];
						if(x&&x<=n&&y&&y<=m)
						{
							int a=ids[i][j],b=ids[x][y],w=dw[u];
							if(a<b)//添加这个条件就不会把一条边存多次了 
								e[k++]={a,b,w};
						}
					}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n*m;i++)
		p[i]=i;
	for(int t=1,i=1;i<=n;i++)//将二维转换为一维
		for(int j=1;j<=m;j++,t++)
			ids[i][j]=t;
	int x1,y1,x2,y2;
	while(cin>>x1>>y1>>x2>>y2)
	{
		int a=find(ids[x1][y1]),b=find(ids[x2][y2]);
		p[find(a)]=find(b);
	}
	get_edges();
	int res=0;
	for(int i=0;i<k;i++)
	{
		int a=find(e[i].a),b=find(e[i].b),w=e[i].w;
		if(a!=b)
		{
			p[a]=b;
			res+=w;
		}
	}
	printf("%d\n",res);
	return 0;
}

1146. 新的开始
题意:有n口井,①在矿井 i 上建立一个发电站,费用为 vi(发电站的输出功率可以供给任意多个矿井)。②将这口矿井 i 与另外的已经有电力供应的矿井 j 之间建立电网,费用为 pi,j。求保证所有矿井电力供应的最小花费方案。                                                                                                         
思路:这道题的妙处在于把在矿井上建立发电站看作是该矿井与一个假想的发电站之间建立连接,所以题意就变为将n+1个矿井连接起来的最小花费。

#include<iostream>
#include<cstring>
using namespace std;
const int N=310,INF=0x3f3f3f3f;
int w[N][N];
int dist[N];//记录每个点到集合的距离
bool st[N];
int n;
int prim()
{
	memset(dist,INF,sizeof(dist));
	dist[0]=0;//第一个进入集合的是不需要花费的
	int res=0;
	for(int i=0;i<=n;i++)
	{
		int t=-1;
		for(int j=0;j<=n;j++)//寻找到集合最小的点
		{
			if(!st[j]&&(t==-1||dist[t]>dist[j]))
				t=j;
		}
		st[t]=true;
		res+=dist[t];
		for(int j=0;j<=n;j++)
			dist[j]=min(dist[j],w[t][j]);
	}
	return res;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)//用0表示超级电站
	{
		cin>>w[0][i];
		w[i][0]=w[0][i];//建立每个电站的费用就是每个电站连接到超级电站的费用
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cin>>w[i][j];
		cout<<prim()<<endl;
	return 0;
}

1145. 北极通讯网络
题意:无线电收发机数量不限,卫星设备数量有限,无线电收发机有一个不同的参数 d,两座村庄之间的距离如果不超过 d,就可以用该型号的无线电收发机直接通讯,d 值越大的型号价格越贵。配备卫星设备的两座村庄无论相距多远都可以直接通讯。现在有 k 台卫星设备,请你编一个程序,计算出应该如何分配这 k 台卫星设备,才能使所配备的无线电收发机的 d 值最小。

思路:题意可以转换为删去权值大于d的边之后,使整个图形的连通块个数不大于k。用kruskal算法,一开始连通块的个数为n,每将一条边加入之后,连通块个数减一n--,最后当n<=k时,此时新加入的边的权值就是最小的d。
①怎么将坐标转换为不同的点来表示?用1~n来代表各个村庄。
②Kruskal算法,先按边的权值进行排序,所以有个固定的结构,用结构体来表示边。
③prim算法,不断选取当前所有点到集合的最小值将点进入集合,所以有固定的结构dist[i]存储所有未入集合的点到集合的最短距离。


#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=510,M=N*N/2;
typedef pair<int,int> PII;
int p[N];
PII q[N];
int n,m,k;
struct str
{
	int a,b;
	double w;
}e[M];
bool cmp(str x,str y)
{
	return x.w<y.w;
}
double get_dist(PII a,PII b)
{
	double dx=a.first-b.first;
	double dy=a.second-b.second;
	return sqrt(dx*dx+dy*dy);
}
int find(int x)
{
	if(x!=p[x])
		p[x]=find(p[x]);
	return p[x];
}
int main()
{
	cin>>n>>k;
	for(int i=0;i<n;i++)
		cin>>q[i].first>>q[i].second;
	for(int i=0;i<n;i++)//记录村庄之间的距离
		for(int j=0;j<i;j++)
			e[m++]={i,j,get_dist(q[i],q[j])};
	sort(e,e+m,cmp);
	for(int i=0;i<n;i++)
		p[i]=i;
	int cnt=n;//连通块的个数
	double res=0;
	for(int i=0;i<m;i++)
	{
		if(cnt<=k) break;
		int a=find(e[i].a),b=find(e[i].b);
		double w=e[i].w;
		if(a!=b)
		{
			p[a]=b;
			cnt--;
			res=w;
		}
	}
	printf("%.2lf\n",res);
	return 0;
}

346. 走廊泼水节
题意:给一棵n个结点的树添加若干条边,使这棵树成为完全图,并满足图的唯一最小生成树仍是这棵树,求增加的边的权值总和最小是多少?

思路:将给定的边从小到大排序,在将两个点合并为同一集合时,需要另外添加的边数为s[a]*s[b]-1,1是a,b这两个点所在的边是题目给出的树中的边,权值w,这些新增加的边的权值至少为w+1。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=6010;
int n;
int p[N],s[N];
struct str
{
	int a,b,w;
}e[N];
bool cmp(str x,str y)
{
	return x.w<y.w;
}
int find(int x)
{
	if(x!=p[x])
		p[x]=find(p[x]);
	return p[x];
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=0;i<n-1;i++)
			cin>>e[i].a>>e[i].b>>e[i].w;
		sort(e,e+n-1,cmp);
		for(int i=1;i<=n;i++)
			p[i]=i,s[i]=1;
		long long res=0;
		for(int i=0;i<n-1;i++)
		{
			int a=find(e[i].a),b=find(e[i].b),w=e[i].w;
			if(a!=b)
			{
				p[a]=b;
				res+=(s[a]*s[b]-1)*(w+1);//将两个集合合并,需要添加s[a]*s[b]条边,由于a,b是本来就存在的,所以需要为它添加的是s[a]*s[b]-1条边
				s[b]+=s[a];
			}
		}
		cout<<res<<endl;
	}
	return 0;
}

1148. 秘密的牛奶运输
题意:求次小生成树。                                                                                                                        
思路:1、求最小生成树,统计标记那条边是树边,那条边是非树边。
2、预处理任意两点间的边权最大值d[a][b]
3、依次枚举所有非树边,判断非树边的权值是否大于最大边,是的话则交换,否则再与次最大边比较。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1e4+10,M=510;
int p[M],d1[M][M],d2[M][M];
int n,m;
int h[M],e[2*M],w[2*M],ne[2*M],idx;
struct str
{
	int a,b,w;
	bool f;
}edge[N];
bool cmp(str x,str y)
{
	return x.w<y.w;
}
int find(int x)
{
	if(x!=p[x])
		p[x]=find(p[x]);
	return p[x];
}
void add(int a,int b,int c)//邻接表建图
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
//遍历图,将任意两点间的距离都处理为最大值,次最大值
void dfs(int u,int fa,int max1,int max2,int d1[],int d2[])
{
	d1[u]=max1,d2[u]=max2;
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j!=fa)//自环
		{
			int t1=max1,t2=max2;
			if(t1<w[i]) t2=t1,t1=w[i];
			else if(t1>w[i]&&t2<w[i]) t2=w[i];
			dfs(j,u,t1,t2,d1,d2);
		}
	}
}
int main()
{
	memset(h,-1,sizeof(h));
	scanf("%d%d",&n,&m);
	int x,y,z;
	for(int i=0;i<m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		edge[i].a=x,edge[i].b=y,edge[i].w=z;
	}
	sort(edge,edge+m,cmp);
	for(int i=1;i<=n;i++) p[i]=i;
	ll sum=0;
	for(int i=0;i<m;i++)//Kruskal算法
	{
		int a=find(edge[i].a),b=find(edge[i].b),w=edge[i].w;
		if(a!=b)
		{
			p[a]=b;
			sum+=w;
			edge[i].f=true;
			add(edge[i].a,edge[i].b,w);
			add(edge[i].b,edge[i].a,w);
		}
	}
	for(int i=1;i<=n;i++)
		dfs(i,-1,0,0,d1[i],d2[i]);//从fa(-1)指向i的边
	ll res=1e18;
	for(int i=0;i<m;i++)
		if(!edge[i].f)
		{
			int a=edge[i].a,b=edge[i].b,w=edge[i].w;
			if(w>d1[a][b])
				res=min(res,sum+w-d1[a][b]);
			else if(w>d2[a][b])
				res=min(res,sum+w-d2[a][b]);
		}
	printf("%lld\n",res);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值