Grand Prix of Saratov - D. Elevator - DP

题意: 有一台电梯,可以容纳无限容量的人,初始在 0 层,每次可以移动到所载人群中 需要到达的最高层,并最终返回 0 层。每层移动时间为 1,忽略人进出时间。现在有 n n n 名按时间顺序到达 0 层电梯口的人,第 i i i 个人将在 t i t_i ti 时刻到达电梯口,想去楼层 a i a_i ai。现在询问最短花费多少时间使得电梯将所有人运送玩并回到 0 层?

  • 1 ≤ n ≤ 2 × 1 0 5 1\le n\le 2\times 10^5 1n2×105
  • 1 ≤ t i , a i ≤ 1 0 9 1\le t_i,a_i\le 10^9 1ti,ai109

题解: 考虑第 i i i 个人要上到第 a i a_i ai 层,那么排在第 i i i 个人前面的人 j j j,如果他要上的楼层 a j < a i a_jaj<ai,显然可以让第 j j j 个人和第 i i i 个人乘坐同一趟电梯,而时间不会更长。因此把这些人合并以后的 a a a 数组是单调递减的。接着考虑动态规划, f ( i ) f(i) f(i) 表示第 i i i 个人时候电梯启动,把前 i i i 个人送完所花费的最小时间,那么有 f ( i ) = min ⁡ ( max ⁡ ( f ( j ) + 2 × a j + 1 , t i + 2 × a j + 1 ) ) f(i)=\min(\max(f(j)+2\times a_{j+1},t_i+2\times a_{j+1})) f(i)=min(max(f(j)+2×aj+1,ti+2×aj+1)),其中的 max ⁡ \max max 部分是讨论送完 j j j 以后第 j + 1 j+1 j+1 i i i 的人有没有来。

但是这样是 O ( n 2 ) \mathcal{O}(n^2) O(n2) 的,不能接受,考虑怎么优化。注意到 f ( i ) f(i) f(i) 是非递减的,而 a i a_i ai 是递减的,显然对于第二部分 t i + 2 × a j + 1 t_i+2\times a_{j+1} ti+2×aj+1,我们可以维护一个 j j j 使得 k ∈ [ 1 , j − 1 ] k\in[1,j-1] k[1,j1] 均有 f ( k ) ≤ t i f(k)\le t_i f(k)ti,初始让 f ( i ) = t i + a j + 1 f(i)=t_i+a_{j+1} f(i)=ti+aj+1,接着只需要讨论 min ⁡ ( f ( j ) + 2 × a j + 1 ) \min(f(j)+2\times a_{j+1}) min(f(j)+2×aj+1),这东西可以用优先队列维护,但是注意必须满足 f ( j ) > t i f(j)>t_i f(j)>ti 成立,因为 t t t 也是单调递增,如果存在 f ( j ) < t i f(j)f(j)<ti,显然对于后面任何的 k > i k>i k>i 均有 f ( k ) < t k f(k)f(k)<tk,因此直接pop它就行。

代码: 时间复杂度是 O ( n log ⁡ n ) \mathcal{O}(n\log n) O(nlogn)

#include 
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
struct node {
    int t, a;
} p[maxn];
ll f[maxn];
int main() {
    int n;
    while (~scanf("%d", &n)) {
        for (int i = 1; i <= n; i++) scanf("%d%d", &p[i].t, &p[i].a);
        vector v;
        for (int i = n; i >= 1; i--) {
            if (v.empty() || p[i].a > v.back().a) v.push_back(p[i]);
        }
        int m = v.size();
        reverse(v.begin(), v.end());
        priority_queue, vector>, greater>> q;
        int id = 0;
        for (int i = 0; i < m; i++) {
            while (id < i && f[id] <= v[i].t) id++;
            f[i] = 2LL * v[id].a + v[i].t;
            while (!q.empty()) {
                pair now = q.top();
                if (now.second <= v[i].t) q.pop();
                else {
                    f[i] = min(f[i], now.first);
                    break;
                }
            }
            if (i < m - 1) q.push({f[i] + 2LL * v[i + 1].a, f[i]});
        }
        printf("%lld\n", f[m - 1]);
    }
    return 0;
}

你可能感兴趣的:(Codeforces,动态规划(DP),单调队列)