其实只进行一次连发的 $f_i$ 不用去 dp,直接二分一下就行,这样会运行得更快。

思路

我们容易注意到,每进行完一次连发之后,两个炮都要重新进入一轮冷却,相当于我们面对的是一个血量更少的敌人,这构成了一个新的子问题。我们只需要考虑在打掉怪物多少血量的时候连发,可以让总时间最小。这显然是一个 dp 问题,定义 $f_i$ 表示只能连发一次并且打掉 $i$ 的血量所需要的最小时间,$g_i$ 表示连发多次并且打掉 $i$ 的血量所需要的最小时间。容易得到 dp 方程如下 :

$$ g_i = \min_{j < i}\lbrace g_j + f _{i - j}\rbrace $$

对于 $f_i$ 我们二分枚举所需时间,这样更方便一些。

但是有一种情况,一个炮冷却时间特别长,所以连发不如单个打划算。我们在二分的时候把这种情况考虑一下就行。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define int long long
const int inf = 0x3f3f3f3f3f3f3f3fll;
int p1, p2, t1, t2, h, s;
int f[10010], g[10010];
int chk(int x) {
if (x < t1 || x < t2) return x / t1 * (p1 - s) + x / t2 * (p2 - s);
int ret = 0;
ret += p1 + p2 - s;
int r1 = x - t1, r2 = x - t2;
ret += r1 / t1 * (p1 - s) + r2 / t2 * (p2 - s);
if (ret < 0) ret = inf;
return ret;
}
void solve() {
p1 = read(), t1 = read(), p2 = read(), t2 = read();
h = read(), s = read();
for (int i = 1; i <= h; i++) {
int p = 0;
for (int j = (1ll << 60); j; j >>= 1)
if (chk(p + j) < i) p += j;
f[i] = p + 1;
}
memset(g, 0x3f, sizeof(g));
g[0] = 0;
for (int i = 1; i <= h; i++)
for (int j = 0; j < i; j++)
g[i] = min(g[i], g[j] + f[i - j]);
cout << g[h] << endl;
}