codeforces 489e Hiking dp+01分数规划+二分

本文探讨利用分数规划和二分查找解决最小化问题,具体问题背景涉及在给定条件下的路径选择与整体满意度计算。通过数学建模,引入01分数规划的概念,并详细解释如何通过二分查找法寻找到最优解。文章最后提供了C++代码实现,展示了从理论到实践的完整过程。

题意:

一个人在起点0,有n个休息点,每个点有两个数值,分别表示距离起点的距离xi,以及所获得的愉悦值bi

这个人打算每天走L距离,但实际情况不允许他这么做。

定义总体失望值val = sum(sqrt(Ri - L)) / sum(bi);   Ri:一天所走的距离。i为所到的节点。

现在要使得val最小(这个人必须要到达最终的节点),让你找出相应的方案,即把所应走的休息点输出。


思路:

一开始用dp,结果是错的,当时也感觉有点不对。

01分数规划,网上找了一下资料以及让铭神解答了一些疑惑,还得多做几题才能熟悉。


对于01分数规划,其实就是求解分数的最值问题。

假如对于原来的式子val = sum(a[i]*x[i])/sum(b[i]*x[i]);//x[i]:1或者0,表示走或不走这个点;

假设answer就是最优值,放到这题来说,也就是说是最小值;为了方便起见,这里的A、B代表最优的方案)

answer = A/B; 

那么对于所有方案有:

answer <= sum(a[i]*x[i])/sum(b[i]*x[i])  ①

移项之后:

sum(a[i]*x[i]) - sum(b[i]*x[i])*answer >= 0 ②

由式子②可看出,最终的answer要使得所有的方案均>=0;


由于现在不知道answer究竟是多少,我们先假设一个值,记为L;

F(L) = sum(a[i]*x[i]) - sum(b[i]*x[i])*L;

可以看出,函数F(L)在方案确定的情况下,L值越大,F(L)值越小,是一个单调递减函数。

但实际上,即使方案不固定,F(L)的值也是随着L的增大而减少。

(图中斜线代表方案,横坐标代表变量L)当L增大的时候,所有的方案F(L)值都会减少。


既然这样,我们就可以二分L来找answer。


二分L,找到使sum(a[i]*x[i])-sum(b[i]*x[i])*L 的最小的方案(对于本题,这个就是用dp实现)

*以下用A[i]表示sum(a[i]*x[i]),B[i]表示sum(b[i]*x[i]);

如果F(L) > 0,则有L < A[i]/B[i](虽说此时的L已经使所有方案均>=0,但此时的L是取不到的。因为A[i]/B[i]已经是当前最小的了,而L却更小)此时应该把下界上移,即l = mid;

如果F(L)< 0,则有L > A[i]/B[i],说明L太大,其实可以再小一点。因为A[i]/B[i]就是更优的方案。此时应该把上界下移,即r = mid;


类似的,如果是求最大值,则只要把式子①改为”>=“。


*由于也才刚学的01分数规划,本想说得更通用点,限于目前的理解,只能到这里。如果上文有哪里不对的话,还望指正。

code:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int MAXN = 1e3+5;
const int INF = 0x3f3f3f3f;

int n, l;
int x[MAXN], b[MAXN];
int par[MAXN];
double dp[MAXN];

void getRoute(int i)
{
    if(i == 0) return;
    getRoute(par[i]);
    cout<<i<<" ";
}
bool check(double L)
{
    dp[0] = 0;
    for(int i = 1;i <= n; i++)
    {
        dp[i] = INF;
        for(int j = 0;j < i ;j++)
        {
            double tmp = dp[j] + sqrt(1.0 * abs(x[i]-x[j]-l)) - L*b[i];
            if(tmp < dp[i])
            {
                dp[i] = tmp;
                par[i] = j;
            }
        }
    }
    if(dp[n] < 0) return true;
    return false;
}
    
void solve()
{
    double l = 0, r = 1e10;
    while(r-l > 1e-8)
    {
        double mid = (l+r)/2;
        if(check(mid))
            r = mid;
        else 
            l = mid;
    }
    getRoute(n);
    cout<<endl;
}
            
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>l;
    for(int i = 1;i <= n ;i++)
        cin>>x[i]>>b[i];
    solve();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值