P2179 [NOI2012] 骑行川藏 题解

文章讲述了通过分析题目的约束条件,推导出将能量加到性价比最高的路段以减少总时间的策略。利用导数确定每段路程的能量与时间关系,并通过二分法求解最优公共导数,从而找到最小化总时间的解决方案。代码示例展示了具体的实现过程。
摘要由CSDN通过智能技术生成

0x01 分析题目

做题首先要排除文中的冗余信息,用最短的语言描述一道题。

对于这道题,我们可以在阅读后得到这样的三条信息:

E i = k i ( v i − v i ′ ) 2 s i E_i = k_i(v_i-v_i^{'})^2s_i Ei=ki(vivi)2si

∑ i = 1 n E i ≤ E \sum_{i=1}^{n}E_i \leq E i=1nEiE

要求 t t t 尽可能小。

0x02 做法的推导

初步分析完后,我们就要着手从初始条件开始推答案。

在两条初始信息中,信息 3 是和答案有关的,信息 1、2 是约束条件,所以我们的思路自然转移到如何将约束条件与答案关联到一起。

使用脑子思考可以得到一个结论:

每段路上消耗的能量(做的功) E i E_i Ei 越大,消耗的时间 t i t_i ti 越小。

上面这句话换种方式表达,就有了:

我们可以在某段路上增加能量以缩短时间。(推论 1)

有了推论 1,我们就将约束条件与答案关联到一起了。

但又由推论 1 引出了一个新的问题:在哪条路上增加能量呢?

由于增加某条路的能量不会干扰到另一条路,所以我们可以贪心的想:

把能量加到价值(或说性价比)最高的路上去。

那对于一条路来说,此刻增加能量的价值又如何体现呢?

显然是增加的时间。

于是我们很容易想到:若时间是关于能量的函数,那么函数在 E i E_i Ei 处的切线的斜率,也就是导数,就是在此条路增加能量的价值(性价比)。

推论 1 告诉我们: t t t E E E 的增大而减小。

由此可以知道:价值(或性价比)只会越来越小。

我们总会将能量加到性价比大的路段上去,不断如此操作,我们可以想象最后所有路的价值(或性价比)会是一个相同值!

于是我们可以开始推式子了!

d t d E = x \frac{dt}{dE} = x dEdt=x

x x x 为最后的公共导数。

因为 E i = k i ( v i − v i ′ ) 2 s i E_i = k_i(v_i-v_i^{'})^2s_i Ei=ki(vivi)2si,且 t t t 也可以表达为关于 v v v 的函数 t = s v t=\frac{s}{v} t=vs

所以:

d t d E = d t d v d v d E = d t d v / d E d v = x \frac{dt}{dE} = \frac{dt}{dv}\frac{dv}{dE} = \frac{dt}{dv} / \frac{dE}{dv} = x dEdt=dvdtdEdv=dvdt/dvdE=x

d t d v = − s v 2 , d E d v = 2 k s ( v − v ′ ) \frac{dt}{dv} = -\frac{s}{v^2} , \frac{dE}{dv} = 2ks(v-v^{'}) dvdt=v2s,dvdE=2ks(vv)

所以:

− s 2 k s v 2 ( v − v ′ ) = x -\frac{s}{2ksv^2(v-v^{'})} = x 2ksv2(vv)s=x

如果我们知道了公共导数 x x x,就可以解出每段路的 v v v 值(懒得解方程?直接二分 v v v 不就行了)。

知道了每段的 v v v 值,就可以检验当前的 x x x 是否可行。

于是, x x x 也可以二分了。

最后看看代码,就很容易明白了。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
double s[10005], k[10005], u[10005];
double e;
double solve(double x, int i) { //公共导数x与当前路段i
	double l = 0, r = 100005, v;//题目保证v<=100005
	int batch = 60;//浮点数二分的批次
	while(batch--) {
		v = (l + r) / 2;
		if(2 * k[i] * s[i] * x * v * v * (v - u[i]) > -s[i]) l = v;//刚推导出来的式子稍加变形
		else r = v;
	}
	v = (l + r) / 2;//最后得到结果
	return v;
}
double work(double x) {//二分出的公共导数得到的能量总值
	double sum = 0;
	for(int i = 1; i <= n; i++) {
		double v = solve(x, i);
		sum += k[i] * s[i] * (v - u[i]) * (v - u[i]);
	}
	return sum;
}
signed main() {
	cin>>n>>e;
	for(int i = 1; i <= n; i++) {
		cin>>s[i]>>k[i]>>u[i];
	}
	double l = -0x3f3f3f, r = 0, dc;//注意:函数t呈下降趋势,导数显然为负
	int batch = 100;
	while(batch--) 
	{
		dc = (l + r) / 2;
		if(work(dc) <= e) l = dc;
		else r = dc;
	}
	dc = (l + r) / 2;
	double ans = 0;
	for(int i = 1; i <= n; i++)
		ans += s[i] / solve(dc, i);
	printf("%.12lf\n", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值