最小比例生成树(01分数规划)二分或迭代

本文探讨了一位国王为了连接各个村庄而修建水渠的问题。通过分析成本和距离的关系,利用最小生成树算法,采用二分法和迭代法求解最小比率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://poj.org/problem?id=2728

Desert King
Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 20513 Accepted: 5764

Description

David the Great has just become the king of a desert country. To win the respect of his people, he decided to build channels all over his country to bring water to every village. Villages which are connected to his capital village will be watered. As the dominate ruler and the symbol of wisdom in the country, he needs to build the channels in a most elegant way. 

After days of study, he finally figured his plan out. He wanted the average cost of each mile of the channels to be minimized. In other words, the ratio of the overall cost of the channels to the total length must be minimized. He just needs to build the necessary channels to bring water to all the villages, which means there will be only one way to connect each village to the capital. 

His engineers surveyed the country and recorded the position and altitude of each village. All the channels must go straight between two villages and be built horizontally. Since every two villages are at different altitudes, they concluded that each channel between two villages needed a vertical water lifter, which can lift water up or let water flow down. The length of the channel is the horizontal distance between the two villages. The cost of the channel is the height of the lifter. You should notice that each village is at a different altitude, and different channels can't share a lifter. Channels can intersect safely and no three villages are on the same line. 

As King David's prime scientist and programmer, you are asked to find out the best solution to build the channels.

Input

There are several test cases. Each test case starts with a line containing a number N (2 <= N <= 1000), which is the number of villages. Each of the following N lines contains three integers, x, y and z (0 <= x, y < 10000, 0 <= z < 10000000). (x, y) is the position of the village and z is the altitude. The first village is the capital. A test case with N = 0 ends the input, and should not be processed.

Output

For each test case, output one line containing a decimal number, which is the minimum ratio of overall cost of the channels to the total length. This number should be rounded three digits after the decimal point.

Sample Input

4
0 0 0
0 1 1
1 1 2
1 0 3
0

Sample Output

1.000

题意:给出n个村庄的坐标(x,y,z),z代表海拔高度,一个国王要修一些水渠,是这些村庄联通起来,任意连个村庄之间的距离是他们水平坐标的欧几里得距离,花费是海拔的高度差,问联通后的所有花费和比上所有距离和最小是多少;

分析:设第i条边的费用是c[i],距离是l[i],由此可得出公式:r=sigma(c[i]*x[i])/sigma(l[i]*x[i]);(x[i]=0或者1表示是否要取该边)

设在z(l)=sigma(c[i]*x[i])-l*sigma(l[i]*x[i]);

变形得:r=sigma(c[i]*x[i])/sigma(l[i]*x[i])=l+z(l)/sigma(l[i]*x[i]);

首先给l附上一个较大的值。

当r有比l更小值的充要条件是z(l)<0,而要求z(l)的最小值,就是求sigma(c[i]*x[i])-l*sigma(l[i]*x[i])最小生成树,边的权值是d[i]=c[i]-l*l[i];然后用求最小生成树的方法来更新z(l)的值;

每次当z(l)有小于0的值时,就说明还存在比当前l更小的比率,然后就把比l较小的l1来更新边权值,再次求z(l1),当l越来越收敛于r时,z(l)也就趋于0;所以当z(l)=0的时候也就是比率最小的时候;

下面介绍两种方法一种是二分法(2400ms)和迭代法(255ms)

二分法:

首先去两个极端值left=0和right=100000即可,然后二分l值mid=(left+right)/2;当得到的z(mid)<0时,说明还有更优的答案此时right=mid,当z(mid)>0时,此时的mid是没有任何意义的,所以left=mid;知道right-left的值小于eps=0.00000001的精确度的时候mid就是最优答案;

迭代法:

首先给l随便附一个值a=0,然后建边求最小生成树z(a)的值,令b=z(l);加入fabs(b-a)<eps=0.000001的精确度时,b就是最优解,否则把b赋给a,然后重新建边求z的值,知道满足精确度是退出循环;

代码:

二分法:

#include"stdio.h"
#include"string.h"
#include"queue"
#include"stdlib.h"
#include"iostream"
#include"algorithm"
#include"string"
#include"iostream"
#include"map"
#include"math.h"
#define M 1005
#define eps 1e-6
#define inf 100000000
using namespace std;
double G[M][M],dis[M];
int use[M];
double prime(int n)
{
    double sum=0;
    memset(use,0,sizeof(use));
    int i,j;
    for(i=1;i<=n;i++)
        dis[i]=G[1][i];
    dis[1]=0;
    use[1]=1;
    for(j=1;j<n;j++)
    {
        double Min=inf;
        int tep=-1;
        for(i=1;i<=n;i++)
        {
            if(!use[i]&&Min>dis[i])
            {
                Min=dis[i];
                tep=i;
            }
        }
        if(tep==-1)
            break;
        use[tep]=1;
        sum+=Min;
        for(i=1;i<=n;i++)
        {
            if(!use[i]&&dis[i]>G[tep][i])
                dis[i]=G[tep][i];
        }
    }
    return sum;
}
double x[M],y[M],z[M];
double L[M][M],C[M][M];
int main()
{
    int n,i,j;
    while(scanf("%d",&n),n)
    {
        for(i=1;i<=n;i++)
            scanf("%lf%lf%lf",&x[i],&y[i],&z[i]);
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                L[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
                C[i][j]=fabs(z[i]-z[j]);
            }
        }
        double left=0;
        double right=10000000;
        double mid;
        while(right-left>eps)
        {
            mid=(left+right)/2;
            for(i=1;i<=n;i++)
                for(j=1;j<=n;j++)
                G[i][j]=C[i][j]-L[i][j]*mid;
            double ans=prime(n);
            if(ans<0)
                right=mid;
            else
                left=mid;
        }
        printf("%.3lf\n",mid);
    }
}
迭代法:
#include"stdio.h"
#include"string.h"
#include"queue"
#include"stdlib.h"
#include"iostream"
#include"algorithm"
#include"string"
#include"iostream"
#include"map"
#include"math.h"
#define M 1005
#define eps 1e-6
#define inf 100000000
using namespace std;
double G[M][M],dis[M];
bool use[M];
double x[M],y[M],z[M];
double L[M][M],C[M][M];
double prime(int n)//注意有几个与prime算法不同的地方
{
    int i,j,k,pre[M];
    double mini,b=0,c=0;
    for(i=1;i<=n;i++)
    {
        dis[i]=G[1][i];
        pre[i]=1;
        use[i]=false;
    }
    dis[1]=0;
    use[1]=true;
    for(i=1;i<n;i++)
    {
        mini=inf;
        k=-1;
        for(j=1;j<=n;j++)
        {
            if(!use[j]&&mini>dis[j])
            {
                mini=dis[j];
                k=j;
            }
        }
        if(mini==inf)break;
        b+=L[pre[k]][k];//*******************
        c+=C[pre[k]][k];//*******************
        use[k]=true;
        for(j=1;j<=n;j++)
        {
            if(!use[j]&&dis[j]>G[k][j])
            {
                dis[j]=G[k][j];
                pre[j]=k;//记录每个节点的前驱
            }
        }
    }
    return c/b;//返回比率
}
int main()
{
    int n,i,j;
    while(scanf("%d",&n),n)
    {
        for(i=1;i<=n;i++)
            scanf("%lf%lf%lf",&x[i],&y[i],&z[i]);
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                L[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
                C[i][j]=fabs(z[i]-z[j]);
            }
        }
        double left=0;
        double right;
        while(1)
        {
            for(i=1;i<=n;i++)
                for(j=1;j<=n;j++)
                G[i][j]=C[i][j]-L[i][j]*left;
            right=prime(n);
            if(fabs(right-left)<eps)break;
            left=right;
        }
        printf("%.3lf\n",right);
    }
    return 0;
}


转载于:https://www.cnblogs.com/mypsq/p/4348182.html

<think>好的,用户想了解最优比率生成树的概念和实现方法。首先,我需要回忆一下相关知识。最优比率生成树是图论中的一个问题,通常应用在需要权衡两个不同权重的边的情况下,比如成本和收益。用户提供的引用里提到了二分搜索和最小生成树算法结合的方法,这可能涉及到01分数规划。 首先,得解释清楚什么是最优比率生成树。比如,每条边有两个权重,比如成本和长度,我们需要找到一个生成树,使得总成本与总长度的比率最小。这里需要明确问题的定义,可能用户需要例子来说明。 接下来是算法实现。根据引用中的信息,常用的是二分搜索加上PrimKruskal算法。可能的具体步骤应该是:确定比率范围,通过二分法不断调整目标比率,将边权重新计算为a - r*b,然后检查是否存在生成树使得总权值非负。如果存在,说明当前比率可以更小,否则需要增大比率。这样循环直到达到精度要求。 需要注意的几个点:二分法的初始上下界如何确定,边权的转换方式,以及生成树算法的选择。引用里提到可能用最大生成树,这取决于问题的设定,比如当需要最大化某个比率时,可能需要调整方向。 另外,用户可能需要具体的例子者伪代码来理解实现过程。比如,如何将原问题转化为子问题,如何在每次二分迭代中生成新的边权,以及如何判断是否存在满足条件的生成树。 还要考虑时间复杂度和优化方法。比如,二分法的次数取决于精度要求,而每次生成树的算法如果是Prim的O(),总复杂度会是O(n² log r),这在处理大规模数据时可能需要优化。 可能的相关问题会涉及算法步骤的详细解释、与其他生成树算法的对比、应用场景以及实际实现中的注意事项。需要确保回答覆盖这些方面,同时引用提供的资料来支持解释,比如引用中的案例如何应用该算法。 最后,检查LaTeX格式是否正确,所有数学表达式用$...$$$...$$,并且结构清晰,符合用户的要求。同时生成的相关问题要针对用户可能想进一步了解的方面,比如应用、优化复杂度分析。</think>### 最优比率生成树解释与实现方法 #### 概念定义 **最优比率生成树**是图论中一类特殊的生成树问题,其目标是在带权无向图中选择一棵生成树,使得两个边权集合的比率达到最优(最小最大)。例如,每条边$e_i$有成本$c_i$和收益$d_i$,要求找到生成树$T$使得总成本与总收益的比率最小: $$ \text{最小化} \quad r = \frac{\sum_{e \in T} c_e}{\sum_{e \in T} d_e} $$ 这种问题在资源分配、网络设计等领域有重要应用[^3]。 #### 核心算法:二分搜索+生成树判定 **实现步骤**: 1. **初始化比率范围**:确定二分搜索的上下界$[low, high]$,例如根据边的权值设定。 2. **二分迭代**:每次取中间值$mid = \frac{low + high}{2}$,将边权转换为$a_i - mid \cdot b_i$($a_i$和$b_i$为原问题的两个权值)。 3. **生成树判定**:以转换后的边权构建最大生成树(若求最小比率)最小生成树(若求最大比率),计算总权值$z(mid)$。 4. **调整搜索区间**: - 若$z(mid) \geq 0$,说明当前比率$mid$可行,调整上界$high = mid$(最小比率问题)。 - 若$z(mid) < 0$,调整下界$low = mid$。 5. **终止条件**:当$high - low < \epsilon$(预设精度,如$10^{-6}$)时,输出结果。 **代码框架示例**(以Prim算法为例): ```python def optimal_ratio_tree(n, edges, epsilon=1e-6): low, high = 0, 1e5 # 根据实际数据调整 while high - low > epsilon: mid = (low + high) / 2 # 构建转换后的边权 modified_edges = [(a - mid * b, u, v) for a, b, u, v in edges] # 计算最大生成树总权值 total = prim_max(n, modified_edges) if total >= 0: high = mid else: low = mid return high ``` #### 关键点说明 1. **边权转换**:通过引入目标比率$r$,将原问题转化为线性组合权值的生成树问题[^2]。 2. **生成树类型**:若求最小比率,需构建最大生成树;若求最大比率,则需最小生成树。 3. **精度控制**:二分次数通常为$O(\log \frac{r_{\text{max}}}{\epsilon})$,时间复杂度取决于生成树算法的效率(如Prim算法为$O(n^2)$)[^1]。 #### 应用场景 - 通信网络建设:平衡电缆铺设成本与信号传输质量。 - 水资源分配:优化管道成本与输水量比率。 - 物流路径规划最小化运输成本与时间消耗的比值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值