圖論-生成樹-北極通訊網絡(二分答案或最小生成樹)【淺顯易懂的思維過程】

自己想到的一種解法:二分答案
因爲答案具有單調性(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章