[NOI2012]骑行川藏

280 篇文章 1 订阅
89 篇文章 0 订阅

题目

传送门 to luogu

思路

用微分的思想,先把某一条路上 v v v 不同的部分拆开。这样我们就改成了讨论很多很多段,每一段内的 v v v 都是相同的。

如果你做过函数这道题,或许你会有点思路。可以考虑成,把能量分配给每一段路,使得它们对应算出的时间最小。

我们试着算一算导数行吧?然而不是很好算。你会发现 t t t E E E 没有直接关联,而是用 v v v 串联在了一起。所以有一个技巧,我们以 v v v 为自变量,计算 t t t E E E 的导数,然后作比。感性的式子是

d t d E = d t d v ÷ d E d v \frac{dt}{dE}=\frac{dt}{dv}\div\frac{dE}{dv} dEdt=dvdt÷dvdE

颇有些洛必达的味道。

然后就很好算了,因为 E ( v ) = k s ( v − v ′ ) 2    ⇒    E ′ ( v ) = 2 k s ( v − v ′ ) E(v)=ks(v-v')^2\;\Rightarrow\;E'(v)=2ks(v-v') E(v)=ks(vv)2E(v)=2ks(vv) t ( v ) = s ⋅ v − 1    ⇒    t ′ ( v ) = − s ⋅ v − 2 t(v)=s\cdot v^{-1}\;\Rightarrow\;t'(v)=-s\cdot v^{-2} t(v)=sv1t(v)=sv2 于是

t ′ ( v ) E ′ ( v ) = − s ⋅ v − 2 2 k s ( v − v ′ ) = − 1 2 k v 2 ( v − v ′ ) \frac{t'(v)}{E'(v)}=\frac{-s\cdot v^{-2}}{2ks(v-v')}=\frac{-1}{2kv^2(v-v')} E(v)t(v)=2ks(vv)sv2=2kv2(vv)1

默认大家都会复合函数求导啥的。

因为 v > v ′ v>v' v>v ,所以该斜率单增(但始终为负数)。既然斜率是单增的,就容易证明 最终每一段路的导数相等。当然,在这道题中,你可以大致把 t − E t-E tE 函数想象为反比例函数在第一象限的图像。

由于这个导数跟 s s s 无关,所以同一段路拆成多个小段(文章开头的操作)是没用的,它们最终的 v v v 相等。

二分这个斜率。计算所需能量。如果花费的能量偏少了,那么调大斜率(这个函数是反比例),否则调小。

然鹅,怎么解关于的 v v v 的方程 − 1 2 k v 2 ( v − v ′ ) = x \frac{-1}{2kv^2(v-v')}=x 2kv2(vv)1=x 呢?我们已经证明了它单调,二分就行。不会真的有人背得下来三次方程求根公式吧?

复杂度 O ( n log ⁡ 2 Q ) \mathcal O(n\log^2 Q) O(nlog2Q) ,但是 Q Q Q 是什么呢?由于 E ≤ 1 0 8 E\le 10^8 E108 ,所以 v ≤ 1 0 4 + v ′ < 10100 v\le 10^4+v'<10100 v104+v<10100 ;由于答案不超过 1 0 5 10^5 105 ,所以 v ≥ s 1 0 5 ≥ 1 0 − 5 v\ge\frac{s}{10^5}\ge 10^{-5} v105s105 。所以导数 x x x 的范围是

1 0 − 15 ≤ x ≤ 1 0 15 10^{-15}\le x\le 10^{15} 1015x1015

这个范围比较粗略。但是这至少告诉你,二分的次数不能太少。我二分 100 100 100 次就会 W r o n g    A n s w e r \rm Wrong\;Answer WrongAnswer 了。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 10005;
double s[MaxN], k[MaxN], v[MaxN];

double getV(int i,double x){
	double L = v[i], R = L+pow(-1/(2*k[i]*x),0.333333);
	R += 100; if(L < 0) L = 0; // v >= 0
	for(int cs=60; cs; --cs){
		double mid = (L+R)/2;
		if(-1/(2*k[i]*x) < mid*mid*(mid-v[i]))
			R = mid; // a bit too large
		else L = mid; // a bit too small	
	}
	return L;
}
double calc(const int &n,double x){
	double sum = 0; // used energy
	for(int i=1; i<=n; ++i){
		double now = getV(i,x);
		sum += s[i]*k[i]*(now-v[i])*(now-v[i]);
	}
	return sum;
}

int main(){
	int n = readint();
	double E; scanf("%lf",&E);
	for(int i=1; i<=n; ++i)
		scanf("%lf %lf %lf",s+i,k+i,v+i);
	double L = -1e10, R = 0;
	for(int cs=200; cs; --cs)
		if(calc(n,(L+R)/2) <= E)
			L = (L+R)/2;
		else R = (L+R)/2;
	double ans = 0;
	for(int i=1; i<=n; ++i)
		ans += s[i]/getV(i,L);
	printf("%.10f\n",ans);
	return 0;
}

后记

题解里有模拟退火的做法,宋队狂喜

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值