LOJ#10065. 「一本通 3.1 例 2」北极通讯网络
题目链接:https://loj.ac/problem/10065
题目描述
原题来自:Waterloo University 2002
北极的某区域共有 nnn 座村庄,每座村庄的坐标用一对整数 (x,yx, yx,y) 表示。为了加强联系,决定在村庄之间建立通讯网络。通讯工具可以是无线电收发机,也可以是卫星设备。所有的村庄都可以拥有一部无线电收发机, 且所有的无线电收发机型号相同。但卫星设备数量有限,只能给一部分村庄配备卫星设备。
不同型号的无线电收发机有一个不同的参数 ddd,两座村庄之间的距离如果不超过 ddd 就可以用该型号的无线电收发机直接通讯,ddd 值越大的型号价格越贵。拥有卫星设备的两座村庄无论相距多远都可以直接通讯。
现在有 kkk 台卫星设备,请你编一个程序,计算出应该如何分配这 kkk 台卫星设备,才能使所拥有的无线电收发机的 ddd 值最小,并保证每两座村庄之间都可以直接或间接地通讯。
例如,对于下面三座村庄:
其中 |AB|=10,|BC|=20,|AC|=10√5≈22.36
如果没有任何卫星设备或只有 111 台卫星设备 (k=0k=0k=0 或 k=1k=1k=1),则满足条件的最小的 d=20d = 20d=20,因为 AAA 和 BBB,BBB 和 CCC 可以用无线电直接通讯;而 AAA 和 CCC 可以用 BBB 中转实现间接通讯 (即消息从 AAA 传到 BBB,再从 BBB 传到 CCC);
如果有 222 台卫星设备 (k=2k=2k=2),则可以把这两台设备分别分配给 BBB 和 CCC ,这样最小的 ddd 可取 101010,因为 AAA 和 BBB 之间可以用无线电直接通讯;BBB 和 CCC 之间可以用卫星直接通讯;AAA 和 CCC 可以用 BBB 中转实现间接通讯。
如果有 333 台卫星设备,则 A,B,CA,B,CA,B,C 两两之间都可以直接用卫星通讯,最小的 ddd 可取 000。
输入格式
第一行为由空格隔开的两个整数 n,kn,kn,k;
第 2∼n+12\sim n+12∼n+1 行,每行两个整数,第 iii 行的 xi,yix_i,y_ixi,yi 表示第 iii 座村庄的坐标 (xi,yix_i, y_ixi,yi)。
输出格式
一个实数,表示最小的 ddd 值,结果保留 222 位小数。
样例
样例输入
3 2
10 10
10 0
30 0
样例输出
10.00
数据范围与提示
对于全部数据,1≤n≤500,0≤x,y≤104,0≤k≤1001\le n\le 500, 0\le x, y\le 10^4, 0\le k\le 1001≤n≤500,0≤x,y≤104,0≤k≤100。
题解:
自己做这道题的时候,YY了一个跟正解一样的思路,但却不知道怎么证。
后来到网上找了找,发现了一篇很好的证明。
当然,只要证出来了,代码就很简单了。
证明如下:
当正向思考受阻时,逆向思维可能有奇效。本题就是这样。知道卫星设备的数量,求最小的收发距离,可能比较困难;如果知道距离求数量,就很简单了。把所有可以互相通讯的村庄连接起来,构成一个图。卫星设备的台数就是图的连通支的个数。
问题转化为:找到一个最小的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,即问题的解。
代码:
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int n,k,x[510],y[510],d[510]; bool vis[510]; int cc(int i,int j){return(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);} int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) { scanf("%d%d",&x[i],&y[i]); d[i]=cc(1,i); } for(int i=1;i<n;i++) { int mi=2e9,k; for(int j=2;j<=n;++j)if(!vis[j]&&d[j]<mi)mi=d[k=j]; vis[k]=1; for(int j=2;j<=n;++j)if(!vis[j])d[j]=std::min(d[j],cc(k,j)); } std::sort(d+1,d+1+n); printf("%.2lf\n",sqrt(d[n-k+1])); return 0; }
谢谢大家!