XJTUOJ #1030 JM的星系战争
题目描述:
今天,带恶人JM掀起了一场超大规模的星系战争,为了阻止疯狂的JM,你需要前往n个星球去招募勇士(为了部落!)。这些星球的编号为1-n,地球(也就是你的初始所在)为1号星球。为了掩人耳目(?)你必须按照编号顺序依次访问这些星球,然后再回到地球上,也就是按照1\(\rightarrow\)2\(\rightarrow\)···\(\rightarrow\)n\(\rightarrow\)1的顺序访问。
当你驾驶飞船访问每个星球时,你的飞船都需要在星球上着陆然后再起飞,这两个过程都是需要耗费大量的燃料的。因为这是一艘神(zhi)奇(zhang)的飞船,它只在着陆和起飞时有大量消耗,至于在星球之间飞行所耗费的燃料,少到可以忽略不计。
对于编号为 \(i\) 的星球,着陆所需的燃料为\(M_1\)/\(a_i\),起飞所需的燃料为\(M_2\)/\(b_i\),其中\(M_1\)和\(M_2\)分别表示着陆或起飞之前飞船的总重。飞船的总重可以表示为\(M=m+f\),其中\(m\)表示飞船的净重,\(f\) 表示飞船上当前搭载的燃料重量。
例如在编号为 \(i\) 的星球上着陆之前有\(m=9,f=3,a_i=8,b_i=2\),那么着陆需要\((9+3)/8=1.5\)吨燃料,而起飞需要\((9+1.5)/2=5.25\)吨燃料,因为着陆以后燃料被消费掉了,起飞之前\(f=1.5\)而不是\(3\)。
注意,整个过程是最开始从地球起飞,然后依次在\(2...n\)号星球着陆然后起飞,最后在地球着陆。
因为JM的军队已经将这些星球上的燃料掠夺一空,所以你没法在路上补充燃料,你的燃料只能在出发前全部装好。
同样是因为JM的军队的疯狂掠夺,现在燃料已经十分紧缺,你希望花费最少的燃料来完成招募任务。请计算访问这些星球并返回地球所需的最少燃料。上述所有重量的计数单位均相同。
输入格式:
第一行两个正整数\(n\)和\(m\),表示星球个数和飞船净重。
接下来\(n\)行,每行两个正整数\(a_i,b_i\),分别表示每个星球的着陆参数和起飞参数,其作用如题目所述。
输出格式:
输出一行一个浮点数表示答案。但是如果无论装载多少燃料都无法完成招募任务,则输出一个整数\(-1\)不带浮点。
当答案的绝对值误差不超过\(10^{-4}\)时即会被评测通过。
保证如果有解,则解小于\(10^{18}\)
思路:
首先,一个很明显的思路是可以二分答案,模拟验证是否可行,但是\(n\)很大,有超时风险。
注意到燃料要最少,所以在地球着陆后剩的燃料为\(0\),这就是我们的末状态。
那么可以想到倒推,既然知道末状态,那么末状态之前一步是什么呢?列方程:\((m+\Delta)/a_1=\Delta\)(\(\Delta\)是着陆消耗的燃料)。
移项得:\(\Delta=m/(a_1-1)\)。
同理,若着陆或起飞后燃料为\(ans\),那么之前燃料的值为\((m+a_i*ans)/(a_i-1)\)或\((m+b_i*ans)/(b_i-1)\)。
代码如下:
#include<cstdio>
#include<cstdlib>
#define db double
#define maxn (int)(2*1e5)+1
db a[maxn],b[maxn];
int main(){
int i,j,n,x,y;
db ans=0,m;
scanf("%d%lf",&n,&m);
for(i=1;i<=n;i++){
scanf("%d%d",&x,&y);
if(x<=1||y<=1){
printf("-1");
return 0;
}
a[i]=x;b[i]=y;
}
ans=m/(a[1]-1);
for(i=n;i>=2;i--){
ans=(m+b[i]*ans)/(b[i]-1);
ans=(m+a[i]*ans)/(a[i]-1);
}
ans=(m+b[1]*ans)/(b[1]-1);
printf("%.6lf",ans);
return 0;
}