標籤:圖論 最小k度限制產生樹
連結:http://poj.org/problem?id=2349
題意:北極有一些村莊,現需要在這些村莊間建立起通訊,有s個衛星頻道,任何兩個擁有衛星頻道的村莊都可以直接通過衛星進行通訊而無視距離,沒有衛星的村莊通過無線電進行通訊,並且這兩個村莊的距離不能超過D,D值取決於無線電收發器的功率,功率越大,D值越大,但價格也越高,出於購買費用和維護費用的考慮,所有村莊的無線電收發器都相同,即D值相同,現要求在保證任意兩個村莊間都能直接或間接通訊,並且D值最小,輸出這個最小值。
就是輸出最小產生樹最大邊的權值,但是衛星頻道是不確定因素。這道題有個很好的解法及證明。
以下大號字摘自IOI2004國家集訓隊論文《最小產生樹演算法及其應用》(吳景嶽):
當正向思考受阻時, 逆向思維可能有奇效。 本題就是這樣。 知道衛星裝置的數量,求最小的收發距離,可能比較困難;如果知道距離求數量,就很簡單了。把所有可以互相通訊的村莊串連起來, 構成一個圖。 衛星裝置的台數就是圖的連通支的個數。
問題轉化為:找到一個最小的 d,使得把所有權值大於 d 的邊去掉之後,連通支的個數小於等於 k。
先看一個定理。 定理 2: 如果去掉所有權值大於 d 的邊後, 最小產生樹被分割成為 k 個連通支,圖也被分割成為 k 個連通支。
證明:
用反證法。假設原圖被分割成 k’ (k‘≠k)個連通支,顯然不可能 k’>k,所以k’<k。 因此在某一圖的連通支中, 最小產生樹被分成了至少兩部分, 不妨設其為T1,T2。因為 T1 和 T2 同屬於一個連通支,所以一定存在 x∈T1,y∈T2,w(x, y)≤d。又因為在整個最小產生樹中,所以 x 到 y 的路徑中一定存在一條權值大於d 的邊(u,v) (否則 x 和 y 就不會分屬於 T1 和 T2 了) , w(x, y)≤d<w(u,v), 所以把(x, y)加入,把(u,v)去掉,將得到一棵總權值比最小產生樹還小的產生樹。這顯然是不可能的。所以,原命題成立。 (證畢)
有了這個定理, 很容易得到一個構造演算法: 最小產生樹的第 k 長邊就是問題的解。
證明:
首先,d 取最小產生樹中第 k 長的邊是可行的。如果 d 取第 k 長的邊,我們將去掉最小產生樹中前 k-1 長的邊, 最小產生樹將被分割成為 k 部分。 由定理 2,原圖也將分割成為 k 部分。 (可行性)
其次, 如果 d 比最小產生樹中第 k 長的邊小的話, 最小產生樹至少被分割成為 k+1 部分,原圖也至少被分割成為 k+1 部分。與題意不符。 (最優性)
綜上所述,最小產生樹中第 k 長的邊是使得連通支個數≤k 的最小的 d,即問題的解。 (證畢)
s為衛星頻道數量,輸出最小產生樹的第s大邊就行了,s為0的時候情況和1一樣,衛星都不起作用(兩個才能相互連訊),需要特判輸出第一大的邊,不過題目中不會給s為0的情況,我就沒有特判
#include<cstring>#include<string>#include<fstream>#include<iostream>#include<iomanip>#include<cstdio>#include<cctype>#include<algorithm>#include<queue>#include<map>#include<set>#include<vector>#include<stack>#include<ctime>#include<cstdlib>#include<functional>#include<cmath>using namespace std;#define PI acos(-1.0)#define MAXN 1000010#define eps 1e-7#define INF 0x7FFFFFFF#define seed 131#define ll long long#define ull unsigned ll#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1struct node{ int u,v; double dis;}edge[MAXN];int father[550];int n,m,cnt,s;double x[550],y[550];double ans[550];bool cmp(node x,node y){ return x.dis<y.dis;}int find(int x){ int t = x; while(father[t]!=t){ t = father[t]; } int k = x; while(k!=t){ int temp = father[k]; father[k] = t; k = temp; } return t;}void kruskal(){ int i,j=0; for(i=1;i<=n;i++) father[i] = i; for(i=0;i<m;i++){ int a = find(edge[i].u); int b = find(edge[i].v); if(a!=b){ father[a] = b; ans[cnt++] = edge[i].dis; j++; if(j>=n-1) break; } }}int main(){ int t,i,j,a,b; scanf("%d",&t); while(t--){ cnt = 0; m = 0; scanf("%d%d",&s,&n); for(i=1;i<=n;i++){ scanf("%lf%lf",&x[i],&y[i]); for(j=1;j<i;j++){ edge[m].u = i; edge[m].v = j; double temp = (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]); edge[m].dis = sqrt(temp); m++; } } sort(edge,edge+m,cmp); kruskal(); printf("%.2f\n",ans[cnt-s]); } return 0;}