P14460 【MX-S10-T1】『FeOI-4』寻雾启示 单调队列优化DP

P14460 【MX-S10-T1】『FeOI-4』寻雾启示

建议先玩几把起床战争了解一下题目背景

题目背景

当我发现所谓醒来其实是另一个梦

梦的出口散不开的浓雾太沉重

题目大意

你 在玩吉吉岛(起床战争)。

现在你在你自己的出生点(后面称为家),你的目标是到达中岛并获取胜利 (虽然游戏不是这么简单的)。你只能选择搭羊毛过去。

你家里有一个刷铁的地方,你可以在那里收集铁锭,然后通过尚店卖出铁锭获取羊毛,一个铁锭可以换一个羊毛(别问为什么,因为1千克铁和1千克羊毛一样重)。刷铁机的工作效率为 \(\dfrac{1}{k}\),即每 \(k\) 秒刷一个铁。

现在你知道你的搭建羊毛并移动的速度 \(t_1\)和直接跑步的速度 \(t_2\),请问你到达中岛的最短时间是多少。

输入格式

第一行共一个正整数 \(T\),表示测试数据组数。

接下来 \(T\) 行,每行共四个正整数 \(m,k,t_1,t_2\),表示一组测试数据。

输出格式

对于每组测试数据,输出一行 \(m\) 个正整数分别表示 \(d=1,2,\dots,m\) 时的答案。


【数据范围】

对于所有测试数据,保证:

  • \(1\leq T\leq 10\)
  • \(1\leq m\leq 10^5\)
  • \(1\leq k,t_1,t_2\leq 10^9\)
测试点编号 \(m \le\) \(k \le\) \(t_1,t_2 \le\) 特殊性质
\(1\) \(1\) \(10^9\) \(10^9\)
\(2,3\) \(10\) ^ \(10\) AB
\(4,5\) \(10\) \(10\) \(10^9\) AC
\(6,7\) \(100\) \(100\) \(100\) A
\(8,9\) \(10^3\) \(10^9\) \(10^9\) ^
\(10\) \(10^5\) ^ ^

特殊性质 A:保证 \(t_1\geq t_2\)
特殊性质 B:\(k = 10^9\)
特殊性质 C:\(t_1 = t_2 = 10^9\)

思路

首先这种题目,它甚至暗示了你使用DP(输出 \(d=1,2,\dots,m\) 的答案),你又有什么理由不写dp呢?

不难设计dp的状态:\(dp_i\) 表示到了距离为 \(i\) 的地点所需要的最小时间。

那么即可得到转移方程式:

\[dp_i = \min\limits_{j=0}^{i - 1}{\max\{dp_j + j \cdot t_2, k * i\} + j \cdot t_2 + (i - j) \cdot t_1} \]

其中:

  1. \(dp_j + j \cdot t_2\) 表示我从家搭到 \(j\) 的最短时间加上从 \(j\) 跑回去的时间
  2. \(k * i\) 表示生成 \(i\) 个铁块的最小时间
  3. \(j \cdot t_2 + (i - j) \cdot t_1\) 表示拿到铁锭换好羊毛后,从家跑到 \(j\) 再搭羊毛到 \(i\) 的时间

这么做显然是 \(O(n^2)\) 的,但是出题人还是太仁慈了,然我们直接拿到了90pts的高分。但是最后还是差了那么10pts。

我们考虑进行优化。

首先我们注意到 \(dp_j + j \cdot t_2\) 是单调递增的。如此一来,我们发现这张图。

也就是说我们会取以 \(k \cdot i\) 为前缀,\(dp_j + j \cdot t_2\) 为后缀的一个函数。这个函数是单调不下降的。

同时我们知道,\(j \cdot t_2 + (i - j) \cdot t_1\) 可以写成 \(i \cdot t_1 + j \cdot (t_2 - t_1)\),然后分情况讨论:

  1. \(t_1 < t_2\):这个时候,我们发现max里面的式子是单增的,而后面加上的都是单减的,所以最短的时候明显就是中间,即第一个 \(j\) 满足 \(dp_j + j \cdot t_2 > k * i\),或者是最后一个 \(j\) 满足 \(dp_j + j \cdot t_2 < k * i\)。两个取最小值即可。又因为刚才说到的式子要和 \(k \cdot i\) 比较,这个也是单增的,所以我们维护一个单调队列,这样转移的时候可以 \(O(1)\) 转移了。

  2. \(t_1 > t_2\):整个式子都是单调的。但是我们可以有更优的想法:我搭路的速度都比跑步的快,那我直接收集够了 \(i\) 个铁锭,换成羊毛一下子直接搭过去不就行了。可以证明这是最优的。

嗯,对,这题就没了。 上代码!

Code

/*
    @ Author:       Eric / Sky__White
    @ Filename:     P14460.cpp
    @ Date:         21/07/2026
    @ Email:        acwing@foxmail.com / 17802535158@163.com
*/

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <functional>
#include <vector>
#include <queue>
#include <deque>

namespace std {
    class Read {
        public:
        template<typename T>
        inline Read operator >> (T & x) {
            T sum = 0; short opt = 1;
            char ch = getchar();
            while(!isdigit(ch)) opt = (ch == '-') ? -1 : 1, ch = getchar();
            while( isdigit(ch)) sum = (sum << 1) + (sum << 3) + (ch ^ 48), ch = getchar();
            x = sum * opt; return *this;
        }
    };
}
#define int long long
#define all(a) a.begin(), a.end()

using namespace std; Read fin;

const int INF = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int m, k, t1, t2; fin >> m >> k >> t1 >> t2;

    vector<int> dp(m + 1, INF);

    dp[0] = 0;

    if (t1 <= t2) {
        for (int i = 1; i <= m; i ++ )
            dp[i] = i * k + i * t1, cout << dp[i] << " ";
        cout << endl;
        return ;
    }


    vector<int> q(m + 1); int f = 0;

    for (int i = 1; i <= m; i ++ ) {
        while(f < i - 1 and q[f + 1] <= i * k + (t1 - t2)) {
            f ++ ;
        }
        dp[i] = max(q[f], k * i) + f * t2 + (i - f) * t1;
        q[i] = i * t2 + dp[i];
    }

    for (int i = 1; i <= m; i ++ )
        cout << dp[i] << " \n"[i == m];
}

signed main() {
    int T; fin >> T;

    while(T -- ) {
        solve();
    }
}
posted @ 2026-07-23 17:24  Skyかざまこうと  阅读(2)  评论(0)    收藏  举报