图论-生成树-北极通讯网络(二分答案或最小生成树)【浅显易懂的思维过程】

自己想到的一种解法:二分答案
因为答案具有单调性(d越大需要的卫星设备就越少),在实数域上二分d的值,检验的办法就是将距离小于等于的所有点对用并查集合并在一起(也就是在一个联通块里了),最后扫一遍算出一共有多少个联通块,如果联通块的块数少于等于k,那么当前d值合法,否则不合法。
还要注意两个小细节,一个是当最后只有一个联通块的时候实际上全图就已经联通了不需要卫星设备,直接返回合法。
还有一个是当只有一台卫星设备的时候和没有卫星设备是一样的。

#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的一颗瓶颈生成树是这样的一棵生成树,在它所有的边中,最大那一条边的权值是尽可能小的(也就是一棵最大的边权最小的生成树)。
也就是说,我们要求解的是一棵最小瓶颈生成树
而我们有这样子的结论:(严谨的证明我也不懂呀,只懂得直观地想象,大家可以自己画出几个例子感受一下)

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

完美,于是我们只需求解最小生成树就可以了。
等等,还有一步,我们还得再考虑一下题中所给的卫星设备到底起到了一个什么作用。
其实,卫星设备起到的作用就是减免几条我们算出的最小生成树上的边。
如果有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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值