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_j + j \cdot t_2\) 表示我从家搭到 \(j\) 的最短时间加上从 \(j\) 跑回去的时间
- \(k * i\) 表示生成 \(i\) 个铁块的最小时间
- \(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)\),然后分情况讨论:
-
\(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)\) 转移了。
-
\(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();
}
}

浙公网安备 33010602011771号