POJ 2728 Desert King(初遇最优比率生成树)

题目链接:http://poj.org/problem?id=2728

题意:给出几个村庄的坐标x[i]和y[i],以及海拔z[i]。要在这些村庄之间建水渠,费用和两个村庄的海拔差成正比,水渠长度和村庄二维坐标(x,y)上的距离成正比,要求一种方案使得(总的花费/总的水渠长度)最小,输出这个最小值,保留三位小数。
 

这是一道0,1分数规划的题目,求的是一棵生成树sigma(dh)/sigma(l) 的最小值(dh是树上两点之间的高度差,l是树上两点之间的边长)。

按0,1分数规划的思路对解进行分析:(设解为R),有sigma(dh)-R*sigma(l)=0, 假设X不是最优解即不能使得(总的花费/总的水渠长度)最小,比R要大一点,那么最优比率生成树使得sigma(dh)/sigma(l) < X,也就是sigma(dh)-X*sigma(l)<0即sigma(dh-X*l)<0所以把两点间的权用dh-X*l来表示,所以必有一棵最小生成树使得生成树的权和<0。同理若X>R,则必有一棵最小生成树使得生成树的权和>0。

所以可以二分出答案。

因为这题是完全图,而且时间卡的很紧,一般用kruskal都T了,而且prim用堆优化也有很多T了,prim+堆复杂度是O( |E|log|V| ),

是完全图所以复杂度是O( V^2log|V| )反而比裸的primO(V^2)要大。

所以这是TLE的代码:

// 各种TLE ...
#include<cstdio>
#include<queue>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#define inf 0x7fffffff
using namespace std;
struct node{int x,y,h;};
typedef pair<double,int >P;
priority_queue<P,vector<P>,greater<P> >que;
node point[1100];
int n;
double dis[1100];
bool vis[1100];
void ini()
{
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&point[i].x,&point[i].y,&point[i].h);
    }
}
double dist(int u,int v,double x)
{
    double dx=point[u].x-point[v].x,dy=point[u].y-point[v].y;
    double ans=abs(point[u].h-point[v].h)-x*sqrt(dx*dx+dy*dy);
    return ans;
}
bool ok_prim(double x)
{
    memset(vis,0,sizeof(vis));
    fill(dis,dis+n+1,inf);
    double res=0;
    dis[1]=0;
    que.push(P(0,1));
    while(!que.empty()){
        P p=que.top();
        que.pop();
        int u=p.second;
        if(vis[u]) continue;
        vis[u]=1;
        res+=dis[u];
        for(int i=1;i<=n;i++){
            if(vis[i]) continue;
            double tmp=dist(u,i,x);
            if(dis[i]>tmp){
                dis[i]=tmp;
                que.push(P(dis[i],i));
            }
        }
    }
    return res>=0;
}
void solve()
{
    double lb=0,ub=1e5;
    while(ub-lb>1e-4){
        double mid=(lb+ub)/2;
        if(ok_prim(mid)) lb=mid;
        else ub=mid;
    }
    printf("%.3f\n",lb);
}
int main()
{
    while(~scanf("%d",&n)&&n){
        ini();
        solve();
    }
    return 0;
}
用裸的prim二分才刚好过:

//Accepted	724K	2438MS
#include<cstdio>
#include<queue>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#define inf 9999999999
using namespace std;
struct node{int x,y,h;};
node point[1100];
int n;
double dis[1100];
bool vis[1100];
void ini()
{
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&point[i].x,&point[i].y,&point[i].h);
    }
}
double dist(int u,int v,double x) // 求两点间dh-X*l的边权 
{
    double dx=point[u].x-point[v].x,dy=point[u].y-point[v].y;
    double ans=abs(point[u].h-point[v].h)-x*sqrt(dx*dx+dy*dy);
    return ans;
}
bool ok_prim(double x)
{
    memset(vis,0,sizeof(vis));
    fill(dis,dis+n+1,inf);
    double res=0;
    dis[1]=0;
    while(true){
        int v=-1;
        for(int u=1;u<=n;u++){
            if(!vis[u]&&(v==-1||dis[u]<dis[v])) v=u;
        }
        if(v==-1) break;
        vis[v]=1;
        res+=dis[v];
        for(int u=1;u<=n;u++){
            if(vis[u]) continue;
            dis[u]=min(dis[u],dist(v,u,x));
        }
    }
    return res>=0;
}
void solve()
{
    double lb=0,ub=1e7;
    while(ub-lb>1e-4){ //二分 
        double mid=(lb+ub)/2;
        if(ok_prim(mid)) lb=mid;
        else ub=mid;
    }
    printf("%.3f\n",lb);
}
int main()
{
    while(~scanf("%d",&n)&&n){
        ini();
        solve();
    }
    return 0;
}

可以看出二分的效率实在不高,0,1规划还有另一个算法 Dinkelbach 算法,类似于牛顿迭代法。算法的具体过程是:随便假设一个X1解(比如假设的这个解比最优解大),然后用同样的思路去求最小生成树,肯定会得到边权和<0,所以不是最优解,但这不是最终的目的,还需要用到这棵生成树,题目让我们求要求一种方案使得(总的花费/总的水渠长度)最小解,所以算出按这棵生成树的方案得到的解X2,这个解必定更靠近最优解(因为这棵树使得sigma(dh)/sigma(l) < X1,所以比X1要优),所以再用X2去求dh-X*l的最小生成树,再按这棵树的方式得到更优解X3,迭代多次后Xn,Xn-1与最优解相差无几,所以Xn-Xn-1<ESP,就求得了最优解。

以上这是这个算法的正确性的认识,但是复杂度的证明很复杂。但是比二分要优。

//732K	344MS
#include<cstdio>
#include<queue>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
struct node{int x,y,h;};
node point[1100];
int n;
double dis[1100];
int pre[1100];
bool vis[1100];
void ini()
{
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&point[i].x,&point[i].y,&point[i].h);
    }
}
double dist(int u,int v,double x)
{
    double dx=point[u].x-point[v].x,dy=point[u].y-point[v].y;
    double ans=abs(point[u].h-point[v].h)-x*sqrt(dx*dx+dy*dy);
    return ans;
}
double prim(double x)
{
    memset(vis,0,sizeof(vis));
    double sh=0,sl=0;
    dis[1]=0;
    vis[1]=1;
    for(int i=2;i<=n;i++){
        dis[i]=dist(1,i,x);
        pre[i]=1;
    }
    while(true){
        int v=-1;
        for(int u=1;u<=n;u++){
            if(!vis[u]&&(v==-1||dis[u]<dis[v])) v=u;
        }
        if(v==-1) break;
        vis[v]=1;
        sh+=abs(point[v].h-point[pre[v]].h);
        double dx=point[pre[v]].x-point[v].x,dy=point[pre[v]].y-point[v].y;
        sl+=sqrt(dx*dx+dy*dy);
        for(int u=1;u<=n;u++){
            if(vis[u]) continue;
            double tmp=dist(v,u,x);
            if(dis[u]>tmp){
                dis[u]=tmp;
                pre[u]=v;
            }
        }
    }
    return sh/sl;
}
void solve()
{
    double a=0,b=0;//随便设一个解0 
    while(1){
        b = prim(a);  //迭代 
        if(fabs(a-b)<1e-7)  break;
        a=b; //迭代 
    }
    printf("%.3f\n",b);
}
int main()
{
    while(~scanf("%d",&n)&&n){
        ini();
        solve();
    }
    return 0;
}



你可能感兴趣的:(POJ 2728 Desert King(初遇最优比率生成树))