You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
9.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## [$AcWing$ $303$. 运输小猫](https://www.acwing.com/problem/content/description/305/)
### 一、题目描述
小 $S$ 是农场主,他养了 $M$ 只猫,雇了 $P$ 位饲养员。
农场中有一条笔直的路,路边有 $N$ 座山,从 $1$ 到 $N$ 编号。
第 $i$ 座山与第 $i1$ 座山之间的距离为 $D_i$。
饲养员都住在 $1$ 号山。
有一天,猫出去玩。
第 $i$ 只猫去 $H_i$ 号山玩,玩到时刻 $T_i$ 停止,然后在原地等饲养员来接。
饲养员们必须回收所有的猫。
每个饲养员沿着路从 $1$ 号山走到 $N$ 号山,把各座山上已经在等待的猫全部接走。
饲养员在路上行走需要时间,速度为 $1$ 米/单位时间。
饲养员在每座山上接猫的时间可以忽略,可以携带的猫的数量为无穷大。
例如有两座相距为 $1$ 的山,一只猫在 $2$ 号山玩,玩到时刻 $3$ 开始等待。
如果饲养员从 $1$ 号山在时刻 $2$ 或 $3$ 出发,那么他可以接到猫,猫的等待时间为 $0$ 或 $1$。
而如果他于时刻 $1$ 出发,那么他将于时刻 $2$ 经过 $2$ 号山,不能接到当时仍在玩的猫。
你的任务是规划每个饲养员从 $1$ 号山出发的时间,使得所有猫等待时间的总和尽量小。
饲养员出发的时间可以为负。
**输入格式**
第一行包含三个整数 $NMP$。
第二行包含 $n1$ 个整数,$D_2,D_3,…,D_N$。
接下来 $M$行,每行包含两个整数 $H_i$ 和 $T_i$。
**输出格式**
输出一个整数,表示所有猫等待时间的总和的最小值。
**数据范围**
$2≤N≤10^5,1≤M≤10^5,1≤P≤100,1≤D_i<1000,1H_iN,0T_i10^9$
**输入样例**
```cpp {.line-numbers}
4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12
```
**输出样例**
```cpp {.line-numbers}
3
```
### 二、分析
题意比较复杂,首先要明白的是 **一个饲养员** **某时刻** 出发,能够 **接走哪些猫**,或者说,要满足什么条件的 **猫** 才能够被 **该饲养员** 接走,显然只需要 **到达** 某只猫所在山的时候,猫已经 **玩完** 了就可以接走猫了。
下面来思考 **某个饲养员**+**某只猫** **常规** 情况:
设饲养员出发的时间是$st$**某只猫** 在第$h_i$座山上玩,并且,要玩到$t_i$时间,该饲养员能够接走该猫需要 **出发时间** + **赶路时间** >= **此猫玩耍的结束时间**
$$\large st + sd_{h_i} >= t_i$$
> ① **$sd$:$d$的前缀和,描述每座山距离一号山的距离,此题中$t_i$不用做前缀和**。
> ② **$t_i<=st+sd_{h_i}$,表示$t_i$小,从时间方向思考,就是先到这个时间点,也就是玩完时间先到达,然后小猫开始等待人来接它,后面的是饲养员的到达时间。**
此时可以接走这只猫,当讨论到具体某只猫时,$sd_{h_i}$和$t_i$都是 **固定** 的,而 **饲养员** 的 **出发时间 $st$ 是可以变化的**。$st$时刻出发,可以接走 **剩下猫** 中的满足下面式子$t_i<=st + sd_{h_i}$的 **所有小猫**。
令:$\large a_i=t_i-sd_{h_i}$,问题变成$st$时刻出发,此饲养员可以接走$a_i<=st$的 **所有小猫**。
**重点** 将 $\large a_i$ **自小到大** **排序**,方便确定饲养员在$st$时刻出发,能够接走 **哪些小猫**
><font color='red' size=4><b>注:</b></font>
极限思考法:我们认为饲养员可以瞬移,不管距离有多远,都可以$0$秒到达。
>
> 为了满足这个要求,那么需要把与每只小猫相关的时间,都 **划归** 到每只小猫的 **游玩** 时间上。
>
> **举栗子**
假设$1$号小猫,原游玩时长为$10$小时,距离$1$号山$8$个长度,那么饲养员在$2$点出发,可以在到达时 **直接** 接走$1$号小猫,可以视为小猫$1$的游玩时间是$10-8=2$,这个$2$,我把它重新定义为 **游玩时间**。
>
> 这个 **游玩时间** 在概念上就有了变化,在几号山就不再重要(因为消化到转换后的 **游玩时间** 里了),按新产生的 **游玩时间** 由小到大进行排序,就是它们被接走的 **顺序**。
>
> 继续思考,如果只有$1$名饲养员,那他必须在 **游玩时间最长** 小猫的结束后,通过 **瞬移大法** 完成接猫工作,这样才能保证前面的小猫也都玩完了,可以一并接走,这样的话,需要晚点出发,除最后一只小猫外,前面的小猫多等一会也是没办法的事,否则无法接走所有小猫。
>
> 如果有$2$个饲养员呢?情况就会好一些:$1$号$2$点出发,接走按 **游玩时间** 由小到大排序后的$1,2,3$小猫,$2$号饲养员$3$点出发,接走$4,5,6$小猫,这样会使小猫的整体等待时间变少。
#### 闫式$DP$分析法
**状态表示**
**集合**
$\large f[i][j]$:前$i$个 **饲养员** 接走 **排好序** 的前$j$只猫的 所有方案
> **注**:此处排好序是指在 **瞬移大法** 的前提下,重新计算的 **游玩时间** 由小到大排序。
**属性**
所有方案的 **最小等待时间和**
**转移方程**
$$\large f[i][j] = min(f[i-1][k] + a_j*(j-k)-(s_j-s_k))\ k \in [0,j)$$
>**注:**
>* 前$i-1$个饲养员 **已经** 接走了 **前$k$只小猫>**$k + 1 \sim j$只小猫由 **第$i$个饲养员** 接走
>
>* $\large a_j$:第$j$只猫的 **玩完时间** **减去** **山的距离**,也就是 **第$i$个饲养员出发的时间**
> 此阶段,需要接走$k+1 \sim j$只小猫,那么第$i$位出发的饲养员,出发时间$st=a_j$。
> **为啥呢**?因为此时接到$j$个小猫时,等待时间为零,如果提前出发,则到达时无法完成接$j$小猫的任务;如果延误出发,则会让$j$号小猫多等,不划算。所以,只能是在$a_j$这个时间出发,最合理。(**贪心**)
* 此区间$[k+1\sim j]$,每只猫的 **等待时间** 分别是$$\large a_j - a_{k+1},a_j - a_{k+2},...,a_j-a_j \\ \Rightarrow
\\a_j \times (j-k) - (a_{k+1} + a_{k+2} + ...+a_j) \\ \Rightarrow \\ a_j \times (j-k)-(s_j-s_k)$$
> $\displaystyle s_j=\sum_{u=1}^{j}a[u],s_k=\sum_{u=1}^{k}a[u],s_j-s_k=\sum_{u=k+1}^{j}a[u]$
设$k$是使得$f_{i,j}$取得最小值的那个$k$【**最佳转移点**】,则$$\large f_{i,j} = f_{i-1,k} + a_j \times (j-k)-(s_j-s_k)$$
**变形原则**
- 当前选择的转移点$k$是变化量,将原始的$k$相关的式子视为 **自变量($x$)** 式子
- 由$k$变化后影响结果的式子 视为 **因变量($y$)** 式子
- 自变量前面的系数,如果是常数的话,那么视为 **斜率**
如此处理后,变形:
$$\large f_{i-1,k}+s_k=a_j\times k+f_{i,j}+s_j-a_j\times j$$
于是我们把转移方程写成了 $y=kx+b$ 的形式,其中:
$\large \left\{\begin{array}{l}
x=k & 自变量\\
y=f_{i1,k}+s_k & 因变量\\
k=a_j & 常数,视为斜率\\
b=f_{i,j}+s_ja_j×j & 在斜率固定情况下y轴截距\\
\end{array}\right.
$
由于 $x,k$ 都是非严格单调递增,可以用单调队列做斜率优化,时间复杂度 $O(M×P)$
> ① 首先要明确 **目标**: 找出 **$min(f[i][j])$**,也就是在给出$i$名饲养员,完成接走$j$只小猫,需要小猫整体的等待时间最少。
> ② $\large k=a_j$,此时$j$是枚举到的固定值,即斜率是固定的,不断向下凸壳逼近,试图找到与下凸壳的交点,此时产生的截距$b$最小
> ② $\large b=f_{i,j}+s_ja_j×j$,当$b$最小时,因为此时$s_ja_j×j$是固定值,也就是$f_{i,j}$最小
#### $Code$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
const int P = 110;
int n, m;
int p;
int q[N];
LL d[N], t[N];
LL a[N], s[N];
LL f[P][N];
int h;
int main() {
cin >> n >> m >> p;
for (int i = 2; i <= n; i++) cin >> d[i], d[i] += d[i - 1]; // 从2开始
for (int i = 1; i <= m; i++) cin >> h >> t[i], a[i] = t[i] - d[h], s[i] = s[i - 1] + a[i];
sort(a + 1, a + m + 1); // 由小到大排序,m只小猫
// dp初始
memset(f, 0x3f, sizeof f);
for (int i = 0; i <= p; i++) f[i][0] = 0; // 出动i个饲养员接0只小猫等待0。将DP TABLE第一列修改为0
for (int i = 1; i <= p; i++) { // 饲养员
int hh = 0, tt = 0; // 哨兵
for (int j = 1; j <= m; j++) { // 遍历小猫
// 凸包的斜率单调上升,并且,直线的斜率也是单调上升
// 准备在下凸壳中找到第一个斜率大于k的直线小于k的全部剔除
while (hh < tt && (f[i - 1][q[hh + 1]] + s[q[hh + 1]] - f[i - 1][q[hh]] - s[q[hh]]) <= a[j] * (q[hh + 1] - q[hh]))
hh++;
// 推出的公式
f[i][j] = f[i - 1][q[hh]] + a[j] * (j - q[hh]) - (s[j] - s[q[hh]]);
// 维护下凸壳 [下面是 k=(y1-y2)/(x1-x)的PK大的干掉。同时为了避免除法采用了十字相乘法变形]
while (hh < tt && (f[i - 1][j] + s[j] - f[i - 1][q[tt]] - s[q[tt]]) * (q[tt] - q[tt - 1]) <= (f[i - 1][q[tt]] + s[q[tt]] - f[i - 1][q[tt - 1]] - s[q[tt - 1]]) * (j - q[tt]))
tt--;
// 加入
q[++tt] = j;
}
}
cout << f[p][m] << endl;
return 0;
}
```
### 三、总结
要注意相减顺序,在去除队首$<=k$的点时
```cpp {.line-numbers}
while (hh < tt && (f[i - 1][q[hh + 1]] + s[q[hh + 1]] - f[i - 1][q[hh]] - s[q[hh]]) <= a[j] * (q[hh + 1] - q[hh]))
hh++;
```
如果是$hh+1 -> hh$ 就是$<=$ ,如果是$hh -> hh+1$就是$>=$ 虽然算斜率,两点谁减谁都一样,但是咱们把分母乘过去,负数不等式要变号。队尾斜率计算同理。