图论O题
题意:空间里有n个点,连接出一条最小生成树保证高度之和/水平长度之和尽可能小。
题解:
考虑把生成树的总权值等价于:每条边的权值(暂时不会计算)
w
i
w_i
wi,每条边选择或者不选择为
x
i
x_i
xi
W
=
∑
i
=
1
n
(
n
−
1
)
x
i
∗
w
i
W=\sum^{n(n-1)}_{i=1}{x_i*w_i}
W=∑i=1n(n−1)xi∗wi
如果最后的答案是
a
n
s
ans
ans,显然满足
W
=
∑
x
i
h
i
∑
x
i
l
i
>
=
a
n
s
W=\frac{\sum{x_ih_i}}{\sum{x_il_i}}>=ans
W=∑xili∑xihi>=ans
可以转换成
∑
x
i
(
h
i
−
a
n
s
∗
l
i
)
≥
0
\sum{x_i(h_i-ans*l_i)}≥0
∑xi(hi−ans∗li)≥0
也就是对于
a
n
s
ans
ans,这样我们可以构建出一个当前最小生成树,如果能满足等于号即是答案。
所以我们需要枚举答案,但是显然是不行的,所以再考虑二分。
显然
a
n
s
ans
ans减小的时候,左式变大,反之变小。也就是当最小生成树的权值和大于
0
0
0的时候。
应该增大
a
n
s
ans
ans,反之减小。
是满足决策单调性的。
这题采用邻接矩阵,因为是
n
2
n^2
n2的边数。同时
d
o
u
b
l
e
double
double二分值得参考。(改变端点的时候直接等于左右端点即可。注意:精度会影响速度。这题保留三位小数,用
1
e
−
4
即
可
1e^{-4}即可
1e−4即可)
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define eps 1e-4
using namespace std;
int n;
double line[1010][1010],height[1010][1010];
const int maxm = 2500000;
const int maxn = 2500;
struct node{
double x,y,z;
}A[1010];
bool vis[maxn];double minc[maxn];
double prim(double x){
memset(vis,0,sizeof(vis));
memset(minc,0x7f,sizeof(minc));
vis[1]=true;
double small,ans=0;int now;
for(int i=1;i<=n;i++)minc[i]=height[1][i]-x*line[1][i];
for(int i=1;i<n;i++){
small=0x7f7f7f7f;
for(int j=1;j<=n;j++)if(minc[j]<small&&!vis[j]){small=minc[j];now=j;}
ans+=small;vis[now]=true;
for(int j=1;j<=n;j++)minc[j]=min(minc[j],height[now][j]-x*line[now][j]);
}
return ans;
}
bool check(double x){
double judge=prim(x);
if(abs(judge-0)<=eps)return true;
if(judge>0)return true;
return false;
}
int main(){
cin>>n;
FOR(i,1,n){
scanf("%lf%lf%lf",&A[i].x,&A[i].y,&A[i].z);
}
double ans,tmp=0;
FOR(i,1,n)FOR(j,i+1,n){
line[i][j]=line[j][i]=sqrt((A[i].x-A[j].x)*(A[i].x-A[j].x)+(A[i].y-A[j].y)*(A[i].y-A[j].y));
height[i][j]=height[j][i]=abs(A[i].z-A[j].z);
tmp=line[i][j]*2+height[i][j]*2;
}
double l=0,r=tmp;
while(r-l>eps){
double mid=(l+r)/2.0;
if(check(mid)){
ans=mid;
l=mid;//>=0
}
else r=mid;
}
printf("%.3lf\n",ans);
}