0%

The 2025 ICPC 德国 German Collegiate Programming Contest——B. Bustling Busride(可以思考你想出来的中间变量和原答案之间有什么关系)

题目大意

题目大意

巴瑟普顿大学(Bithampton)有一条通往市中心的公交线路,沿途设有多个停靠站。当前有 nn 名学生在大学(第 0 站)排队等车,每位学生都有一个确定的目的地站点。

公交车按固定时间间隔发车,第一辆车在时刻 0 到达,之后每隔 rr 秒有一辆车到达。每辆公交车的容量无限,司机可以决定让队列最前端的多少人乘坐当前这辆车。

为了应对拥挤,公交公司规定了特殊的上下车规则:
如果车上有任何一名乘客的目的地是当前站点,则车上所有乘客都必须下车。未到达目的地的乘客随后需要重新上车继续行程。

时间计算规则如下:

  1. 上下车耗时:每有一名乘客上车或下车,公交车都需要等待 ww 秒。
  • 在起点上车:耗时为(上车人数 ×w\times w)。
  • 在途中站点下车:耗时为(车上总人数 ×w\times w)。
  • 在途中站点重新上车:耗时为(继续行程人数 ×w\times w)。
  1. 行驶耗时:站点之间的行驶时间给定。

你的目标是为每辆公交车分配乘客(即决定每次从队首取多少人上车),使得所有学生到达各自目的地的耗时中的最大值最小

输入格式

第一行包含四个整数 n,b,r,wn, b, r, w (1n,b105,1r,w1061 \le n, b \le 10^5, 1 \le r, w \le 10^6),分别表示乘客总数、站点总数、公交车发车间隔、上下车每人次的耗时。
第二行包含 bb 个整数 did_i (1di,di1061 \le d_i, \sum d_i \le 10^6),表示从第 i1i-1 站行驶到第 ii 站所需的时间。
第三行包含 nn 个整数 tit_i (1tib1 \le t_i \le b),按队列顺序给出第 ii 个人的目的地站点编号。

输出格式

输出一个整数,表示让所有人都到达目的地所需的最小的最大时间。

样例数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Input
3 3 20 1
2 2 2
2 3 1

Output
18

Input
4 2 1 10
3 2
1 2 2 1

Output
27

Input
5 3 3 3
2 2 1
3 3 2 1 1

Output
17

样例解释

在第一个样例中,最优策略是让所有 3 个人都乘坐第一辆车(时刻 0 到达)。

  1. 起点(大学):3 人上车,耗时 3×1=33 \times 1 = 3 秒。此时车上人员目的地为 {2,3,1}\{2, 3, 1\}

  2. 行驶至站点 1:耗时 2 秒。到达时刻 3+2=53+2=5

  3. 站点 1:此时有一人(目的地为 1)需要下车。规则要求所有人下车。

  • 3 人下车,耗时 3×1=33 \times 1 = 3 秒。时刻 5+3=85+3=8。该乘客到达。
  • 剩余 2 人(目的地 2, 3)重新上车,耗时 2×1=22 \times 1 = 2 秒。时刻 8+2=108+2=10
  1. 行驶至站点 2:耗时 2 秒。到达时刻 10+2=1210+2=12

  2. 站点 2:此时有一人(目的地为 2)需要下车。

  • 2 人下车,耗时 2×1=22 \times 1 = 2 秒。时刻 12+2=1412+2=14。该乘客到达。
  • 剩余 1 人(目的地 3)重新上车,耗时 1×1=11 \times 1 = 1 秒。时刻 14+1=1514+1=15
  1. 行驶至站点 3:耗时 2 秒。到达时刻 15+2=1715+2=17

  2. 站点 3:最后一人下车。

  • 1 人下车,耗时 1×1=11 \times 1 = 1 秒。时刻 17+1=1817+1=18。该乘客到达。

所有乘客到达时间的最大值为 18。

思路讲解

这个 Opus 4.6给出的提示非常好,就是二分还是二分那个答案,答案就是最大的到达时间。但是,然后我们给出了一个中间量,我们给出了一个中间量,这个中间量是什么呢?这个中间量就是一辆车可以最长运行多长时间。不过我们其实没有意识到一件事情啊,就是其实一辆车可以最长运行多长时间,可以非常自然的使用这个答案推出来

下面这个代码,核心的这个代码中的注释,我认为已经非常详细了。我就不多说了,这道题目核心就是这个 check 函数嘛,这个 check 函数的核心已经在上面讲过了。说白了二分答案,首先需要想的就是是不是一定要二分这个答案?可不可以二分其他的东西?当然了,这道题目当然了,就是如果发现你找了一个东西,但是你发现它好像没那么容易二分,或者你觉得好像有点奇怪。这个时候就需要想这个东西和答案之间是不是有什么关系。比如说我我们想到的这个参数,班次的最长运行时间,那么我们发现其实是和这个答案是有关系的。或者你可以换一种角度,就这个答案往往无法指导我们直接进行选择,进行抉择。我们需要对答案进行一些转化,以便指导我们,进行选择与抉择。

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
auto check=[&](ll b_ans) -> bool {
ll idx=1;
// 第几班次
for (ll run=1;run<=N;++run) {
if (idx>N) {
return true;
}
ll st_run=(run-1)*R_wait;
// 该班次最大运行时间
ll mx_run=b_ans-st_run;
if (mx_run<0 && idx<=N) {
return false;
}
ll cur=0;
ordered_set pbds;
// 我们下面的注释里面详细说了为什么不需要算他的零站上去的那个情况,实际上已经被我们的二乘 pre 乘以 tim 给覆盖了。
// pbds.insert(0);
ordered_pair_set multi_pbds;
ll mx_terminal=0;
while (true) {
if (idx>N) {
return true;
}
// 我们目前是需要算,加上这个 IDX 对于我们目前运行时间的增量。
// 这个增量分为两部分,一个是我们带来的增量,就是我们给等待时间所有的带来的增量。
// 所有的站,所有在我们之前的站的等待时间(包括我们自己这一站),都需要加上我们下车的这个时间。
ll terminal=t_ls[idx];
mx_terminal=max(mx_terminal,terminal);
ll pre=pbds.order_of_key(terminal)+1;
// 你可能会说这个零站点到底是在哪里被加了呢?
// 那么其实就是在这个 add,这个2× pre 乘以 W tim 里面,就我们就加了这个零站点。
// 实际上,PRE 是包含 TERMINAL 这个站的,但是这个人在 TERMINAL 站,他下来以后就不会上去了。
// 所以二乘以 PRE,那么实际上包含他在零站上去的这个情况。他在零站上去的情况正好弥补了
// 我们多算的他在 TERMINAL 站下来的这个情况,
ll add=2*pre*W_tim;
if (pbds.find(terminal)==pbds.end()) {
// 如果这个 terminal 之前没有出现过,那么所有在其之后的也需要贡献对这个 terminal 进行贡献。
// 所以在其之后的原来就在他之后的点需要对这个 terminal 进行贡献。
// 这里我们使用一个pair 实现的 multi pbds 实现。
ll suf=SZ(multi_pbds)-multi_pbds.order_of_key({terminal,INF});
// 所有在其后面的点都需要在该站下去以后再上来,所以说是 2*suf*W_tim
add+=2*suf*W_tim;
}
if (cur+add+pre_sum[mx_terminal]>mx_run) {
break;
}
cur+=add;
pbds.insert(terminal);
multi_pbds.insert({terminal,idx});
++idx;
}
}
// 如果说大于 N 那么就说明所有的乘客都被处理完了。
return idx>N;
};

AC代码

AC
https://qoj.ac/submission/2035388
AC
https://codeforces.com/gym/106129/submission/362933869

心路历程(WA,TLE,MLE……)

被 OPUS 4.6指出来的几个 bug。其实主要就是没有乘二,就是这个 pre 还有这个 suf 没有乘二,绷不住了。忘记了,他不仅要上来,还要下去。就是他不仅要下去,还要再上来。所以相当于这个浪费还是挺多的,需要2,需要乘2。

image