图论-生成树-北极通讯网络【两种解法!】(二分答案或最小生成树)【十分详细】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/TengWan_Alunl/article/details/83002489

自己想到的一种解法:二分答案
在实数域上二分d的值,检验的办法就是将距离小于等于的所有点对用并查集合并在一起(也就是在一个联通块里了),最后扫一遍算出一共有多少个联通块,如果联通块的块数少于等于k,那么当前d值合法,否则不合法。
还要注意两个小细节,一个是当最后只有一个联通块的时候实际上全图就已经联通了不需要卫星设备,直接返回合法。
还有一个是当只有一台卫星设备的时候和没有卫星设备是一样的。
此外,在实数域上的二分最后答案是l还是r似乎并没有什么区别(因为已经满足r-l<eps)了,不清楚事实上是不是这样子。望了解的访客留言分享。

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define random(l,r) ((l)+rand()%((r)-(l)+1))
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int inf=1e9+10,N=1e4+1000;
const double eps=1e-6;
int n,k,x[N],y[N],fa[N];
double Map[N][N];
int getf(int v){ return fa[v]==v?v:fa[v]=getf(fa[v]);}
void merge(int a,int b){
	fa[getf(b)]=getf(a);
}
bool test(double d){
	rep(i,1,n) fa[i]=i;
	rep(i,2,n) rep(j,1,i-1) if(Map[i][j]<=d) merge(i,j);
	int cnt=0;
	rep(i,1,n) if(fa[i]==i) cnt++;
	if(cnt==1) return true;
	if(cnt<=k) return true;
	return false;
}
int main(){
	cin>>n>>k;
	rep(i,1,n) cin>>x[i]>>y[i],Map[i][i]=0;
	rep(i,2,n) rep(j,1,i-1) Map[i][j]=Map[j][i]=sqrt((x[j]-x[i])*(x[j]-x[i])+(y[j]-y[i])*(y[j]-y[i]));
	double l=0.0f,r=15000.0f;
	while(r-l>eps){
		double mid=(l+r)/2;
		if(test(mid)) r=mid; else l=mid;
	}
	printf("%.2lf",l);
	return 0;
}
//实数域上的二分答案 

十分详细的最小生成树写法。为什么会想要写最小生成树呢?原因如下:
为了方便思考,我们先不考虑卫星设备的影响。
那么本题中的图可以看做是一个无向完全图,我们要做的就是选取一些边使得图联通,且要使这些边的最大值最小。
那我们就得确定到底需要选择多少条边。
我们知道一张n个点的无向图最少只需要n-1条边就可以联通,在已经选取n-1条边的使图完全联通了的情况下,再多选取边,所有被选取的边中的最大值一定是单调不减的,也就是不会更优。因此可以确定,我们只需要选取n-1条边使图联通的情况底下去使得最大边的值最小即可。
也就是说,我们就要考虑如何生成一棵使得答案最优的树。
介绍一下“最小瓶颈生成树”的概念:
无向图G的一颗瓶颈生成树是这样的一棵生成树,它最大的边权值在G的所有生成树中是最小的。
也就是说,我们要求解的正是一棵最小瓶颈生成树!
而我们有定理如下:

  1. 最小生成树一定是最小瓶颈生成树
  2. 最小瓶颈生成树不一定是最小生成树

大喊Nice!于是我们只需求解最小生成树就可以了。
等等,还有一步,我们还得再考虑一下题中所给的卫星设备到底起到了一个什么作用。
其实,卫星设备起到的作用就是减免几条我们算出的最小生成树上的边。
如果有k(k>1)台设备,那么我们可以减免k-1条边。(如果只有一台设备的话和没有是一样的)
为什么呢?减免后,原图会由联通变为有k个独立的联通块。我们只需要在每个联通块里各放一台设备全图就联通了。
那我们就删去最小生成树上最大的前k-1条边就好了,那么剩下的最大的一条边就是第k大的边,于是问题转化为求最小生成树的第k大的边。
使用Kruskal算法,若当前边是第n-1-k+1条加入生成树的,直接输出当前边的权值作为答案即可。
另:
我们将任意两点u,v在树中的简单路径上长度最大的一条边称为u与v的最小瓶颈路。(但此题并没有用到该概念)

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
#define random(l,r) ((l)+rand()%((r)-(l)+1))
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int inf=1e9+10,N=600;
const double eps=1e-6;
struct edge{
	int a,b;
	double w;
	bool friend operator < (edge a,edge b){
		return a.w<b.w;
	}
}e[N*N];
int n,k,x[N],y[N],fa[N],l;
int getf(int v){ return fa[v]==v?v:fa[v]=getf(fa[v]);}
void merge(int a,int b){
	fa[getf(b)]=getf(a);
}
int main(){
	ios::sync_with_stdio(false); cin.tie(0);
	cin>>n>>k;
	rep(i,1,n) cin>>x[i]>>y[i],fa[i]=i;
	rep(i,2,n) rep(j,1,i-1) e[++l].a=i,e[l].b=j,e[l].w=sqrt((x[j]-x[i])*(x[j]-x[i])+(y[j]-y[i])*(y[j]-y[i]));
	sort(e+1,e+1+l);
	if(k==0) k=1; int cnt=0;
	rep(i,1,l){
		int faa=getf(e[i].a),fab=getf(e[i].b);
		if(faa==fab) continue;
		cnt++; merge(faa,fab);
		if(cnt==n-1-k+1){
			cout<<fixed<<setprecision(2)<<e[i].w<<endl;
			break;
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/TengWan_Alunl/article/details/83002489
今日推荐