乘电梯(动规+单调队列队头优化)
题目描述
【输入文件】
第一行是电梯的数量和大楼层数。然后每行是一个电梯服务的最低层和最高层。
最多有200个电梯,大楼不超过10000层。
显然问题是有解的。不然你是怎么上去的呢?
【输出文件】
最短时间。精确到5位小数。
输入
输出
样例输入
6 15 4 8 10 14 1 5 7 11 13 15 1 13
样例输出
20.32308
题解
在某不知名的菜oj上我目前是rank1(当然不知道以后怎样哈哈哈,我完全没加快速io,毕竟也没什么卵用)
第一个想法是O(n * m * m)的动规,很容易想到,n是电梯数,m是大楼层数
但是显然不行
反过来看看我们的做法
我们是从上到下枚举,每次枚一个电梯就需要枚该层以上所有的值求最小
发现确定了特定层对应特定电梯时,这个值是只和这个当前状态有关的,和之后递推的状态无关
所以可以在求到每一层的最小后,更新从这层开始能坐的电梯的最小值,枚举到下一层能坐这个电梯时,就可以直接用这个值,还是很好想的
可以类比一下01背包的优化,是取单调队列的队头的思想
#include <stdio.h> #include <string.h> #include <iostream> const double oo = 0x3f3f3f3f; using namespace std; inline void read(int &x) { int c = getchar(); while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar(); } double f[10001], cur[201]; int n, m, l[201], r[201]; inline double calc(double a, double b) {return (a*(a + 1) + b*(b + 1)) / 2 / (a + b + 1);} int main() { read(n), read(m); for(int i = 1; i <= n; ++i) read(l[i]), read(r[i]); fill(cur + 1, cur + 1 + n, oo); for(int i = 1; i <= n; ++i) if(r[i] == m) cur[i] = calc(m - l[i], 0); for(int j = m - 1; j >= 1; --j) { f[j] = oo; for(int i = 1; i <= n; ++i) if(l[i] <= j && j <= r[i]) f[j] = min(f[j], cur[i]); for(int i = 1; i <= n; ++i) if(l[i] <= j && j <= r[i]) cur[i] = min(cur[i], f[j] + calc(r[i] - j, j - l[i])); } printf("%.5lf\n", f[1] + m - 1); return 0; }