题意:
一个人在起点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;
}