【二分+最小生成树】Desert King

传送门:POJ2728


题意:大致就是求一个最优比率生成树,也就是平均值最大或最小

思路:

步骤一

最大(小)化平均值有个经典套路:

这里要求$\frac{\sum\limits_{i\in E}{cost_i}}{\sum\limits_{i\in E}{dist_i}}$尽可能小

假设比率为m

那么$\frac{\sum\limits_{i\in E}{cost_i}}{\sum\limits_{i\in E}{dist_i}}\leqslant m$

$\sum{(cost_i-dist_i\times m)}\leqslant 0$

步骤二

推完上面的以后就可以发现只要假定了一个比率m,

就可以把$(v_i-w_i\times m)$作为新的边权求最小生成树。

那么m可以用二分法或牛顿迭代法来搞定


速度上嘛,一目了然(上为牛顿迭代法,下为二分法)

P1

代码(迭代法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define INF 1e8
bool vis[1030];
double x,dis[1030],dist[1030][1030],cost[1030][1030];
int n,a[1030][4],pre[1030];
double prim(double x){
memset(vis,0,sizeof vis);
vis[1]=1; dis[1]=0;
for (int i=2;i<=n;i++){dis[i]=cost[1][i]-dist[1][i]*x; pre[i]=1;}
double tot_cost=0,tot_dist=0;
for (int i=2;i<=n;i++){
double min_dis=INF;
int k=0;
for (int j=1;j<=n;j++)
if (!vis[j] && dis[j]<min_dis){min_dis=dis[j]; k=j;}
dis[k]=0; vis[k]=1;
tot_cost+=cost[k][pre[k]];
tot_dist+=dist[k][pre[k]];
for (int j=1;j<=n;j++)
if (!vis[j] && dis[j]>cost[k][j]-dist[k][j]*x){dis[j]=cost[k][j]-dist[k][j]*x; pre[j]=k;}
}
return tot_cost/tot_dist;
}
int main(){
do{
scanf("%d",&n); if (n==0) break;
for (int i=1;i<=n;i++) scanf("%d%d%d",&a[i][1],&a[i][2],&a[i][3]);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++){
dist[i][j]=sqrt((a[i][1]-a[j][1])*(a[i][1]-a[j][1])+(a[i][2]-a[j][2])*(a[i][2]-a[j][2]));
cost[i][j]=abs(a[i][3]-a[j][3]);
}
x=1e6;
while (1){
double tt=prim(x);
if (x-tt<1e-6) break;
else x=tt;
}
printf("%.3f\n",x);
}while (1);
return 0;
}

代码(二分法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define INF 1e8
bool vis[1030];
double l,r,mid,dis[1030],dist[1030][1030],cost[1030][1030];
int n,a[1030][4];
double prim(double x){
memset(vis,0,sizeof vis);
vis[1]=1; dis[1]=0;
for (int i=2;i<=n;i++) dis[i]=cost[1][i]-dist[1][i]*x;
double sum=0;
for (int i=2;i<=n;i++){
double min_dis=INF;
int k=0;
for (int j=1;j<=n;j++)
if (!vis[j] && dis[j]<min_dis){min_dis=dis[j]; k=j;}
dis[k]=0; vis[k]=1;
sum+=min_dis;
for (int j=1;j<=n;j++)
if (!vis[j] && dis[j]>cost[k][j]-dist[k][j]*x) dis[j]=cost[k][j]-dist[k][j]*x;
}
return sum;
}
int main(){
do{
scanf("%d",&n); if (n==0) break;
for (int i=1;i<=n;i++) scanf("%d%d%d",&a[i][1],&a[i][2],&a[i][3]);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++){
dist[i][j]=sqrt((a[i][1]-a[j][1])*(a[i][1]-a[j][1])+(a[i][2]-a[j][2])*(a[i][2]-a[j][2]));
cost[i][j]=abs(a[i][3]-a[j][3]);
}
l=0; r=1e6;
while (r-l>1e-6){
mid=(l+r)/2;
if (prim(mid)<=0) r=mid;
else l=mid;
}
printf("%.3f\n",r);
}while (1);
return 0;
}