P9521 [JOISC 2022] 京都观光 题解

Description

有一个 n×mn\times m 的网格,从 (x,y)(x,y) 走到 (x,y+1)(x,y+1) 需要 axa_x 的时间,从 (x,y)(x,y) 走到 (x+1,y)(x+1,y) 需要 byb_y 的时间。

问从 (1,1)(1,1) 走到 (n,m)(n,m) 至少需要多久。

1n,m1051\leq n,m\leq 10^5

Solution

首先直接做不太好做,考虑调整法。

容易发现 (i,j)(k,l)(i,j)\to (k,l) 有两种走法:

  1. (i,j)(i,l)(k,l)(i,j)\to (i,l)\to (k,l)
  2. (i,j)(k,j)(k,l)(i,j)\to (k,j)\to (k,l)

其中第一个代价为 c1=ai(lj)+bl(ki)c_1=a_i\cdot(l-j)+b_l\cdot(k-i),第二个代价为 c2=ak(lj)+bj(ki)c_2=a_k\cdot(l-j)+b_j\cdot (k-i),如果 c1c2c_1\leq c_2,移项后可得:(blbj)(ki)(akai)(lj)(b_l-b_j)\cdot(k-i)\leq(a_k-a_i)\cdot(l-j),等价于 blbjljakaiki\displaystyle\frac{b_l-b_j}{l-j}\leq\frac{a_k-a_i}{k-i}

容易发现把路径折线缩起来后,这些线段在坐标系上的斜率不降。

所以如果知道 xxyy 分别走的连续段,那么每次选择 xxyy 里能走的斜率更小的去走。

现在考虑怎么缩连续段。

先考虑 xx。首先是有 nn 个连续段,如果存在相邻连续段满足第一个大于等于第二个,则由于每次选的是斜率更小的走,所以选了第一个之后一定紧接的是第二个,这两段就能缩在一起。

同时如果对于一个连续段,能够让其从中间劈开后满足斜率不降,则贪心地劈开一定更优。

容易发现满足这个条件的连续段劈法就是所有 (i,ai)(i,a_i) 构成的凸包。

分别求出 aabb 的凸包即可。

时间复杂度:O(n+m)O(n+m)

Code

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <bits/stdc++.h>

// #define int int64_t

using i64 = int64_t;
using pii = std::pair<int, int>;

const int kMaxN = 1e5 + 5;

int n, m, topa, topb;
int a[kMaxN], b[kMaxN];
pii stka[kMaxN], stkb[kMaxN];

pii sub(pii a, pii b) { return {a.first - b.first, a.second - b.second}; }
i64 mul(pii a, pii b) { return 1ll * a.first * b.second - 1ll * a.second * b.first; }

void geta() {
for (int i = 1; i <= n; ++i) {
pii p = {i, a[i]};
for (; topa >= 2 && mul(sub(p, stka[topa]), sub(stka[topa], stka[topa - 1])) >= 0; --topa) {}
stka[++topa] = p;
}
}

void getb() {
for (int i = 1; i <= m; ++i) {
pii p = {i, b[i]};
for (; topb >= 2 && mul(sub(p, stkb[topb]), sub(stkb[topb], stkb[topb - 1])) >= 0; --topb) {}
stkb[++topb] = p;
}
}

void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= n; ++i) std::cin >> a[i];
for (int i = 1; i <= m; ++i) std::cin >> b[i];
geta(), getb();
int x = 1, y = 1;
i64 ans = 0;
for (int i = 1, j = 1; x < n || y < m;) {
bool nxt = 0;
if (x == n || y < m && mul(sub(stkb[j + 1], stkb[j]), sub(stka[i + 1], stka[i])) >= 0) nxt = 1;
if (!nxt) {
ans += 1ll * b[y] * (stka[i + 1].first - stka[i].first);
x += stka[i + 1].first - stka[i].first;
++i;
} else {
ans += 1ll * a[x] * (stkb[j + 1].first - stkb[j].first);
y += stkb[j + 1].first - stkb[j].first;
++j;
}
}
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P9521 [JOISC 2022] 京都观光 题解
https://sobaliuziao.github.io/2025/03/29/post/34823f90.html
作者
Egg_laying_master
发布于
2025年3月29日
许可协议