题解 P7913 [CSP-S 2021] 廊桥分配

· · 题解

提供一种 **仅使用普及知识点,不用优先队列、`set`、`pair`、线段树、树状数组、分块** 的做法。 [题目链接](https://www.luogu.com.cn/problem/P7913) 我们从样例 $1$ 入手。 ![当你看到这行文字时,这张图片已经挂了。](https://cdn.luogu.com.cn/upload/image_hosting/huhkczmn.png) 观察图片,不难看出能共用一个廊桥的飞机一定是在时间上没有重叠的(例如上图国内航班第 $1,3, 5$ 架)。这启示我们将所有飞机不重不漏地分组,使得每个组内的飞机都可以用一个廊桥解决停靠问题。 由于国内航班和国际航班是各自独立的,在此只用国内航班说明。用 $arr_i$ 和 $lea_i$ 表示第 $i$ 架飞机的抵达、离开时刻,$id_i$ 表示第 $i$ 架飞机是否已被分组。首先将所有飞机按照抵达时间从小到大排序(下文所指均为排序后的飞机),然后从 $1$ 开始枚举飞机。 还是以样例 $1$ 为例,不妨把飞机 $1\;\left[ 1,5 \right]$ 分到第 $1$ 组,并把 $id_1$ 修改为 $1$。因为同组内飞机没有时间冲突,所以下一架可选飞机 $3\;\left[6,10\right]$、$4\;\left[9,14\right]$、$5\;\left[13,18\right]$。应该选哪一架呢?飞机遵循“先到先得”原则,贪心地想,当飞机 $3$ 降落时,至少有一座廊桥是空闲的(即飞机 $1$ 使用的),而它在哪里停靠都是一样的,因此把它和飞机 $1$ 分为一组不会改变答案。推广一下,假设飞机 $i$ 已经被分组,同组内的下一架应是所有满足 $arr_x > lea_i \; \land \; id_x = 0$ 中抵达时间最早的。由于事先已将飞机排序,可以用二分找出第一架抵达时间晚于 $lea_i$ 的飞机 $x$。不断重复这一过程,如果没有飞机可选,则这一组已经分完。 注意到 $id$ 数组并没有单调性,即有可能 $id_x \ne 0$。若从 $x$ 向后暴力枚举,复杂度可能退化成 $O\left(n^2\right)$,显然无法接受。我们借用并查集的路径压缩思想来巧妙地解决这一问题。定义 $c_i$ 为第一架降落时间晚于 $arr_i$ 且没有组的飞机编号,初始时 $c_i \gets i + 1$。重复执行 $x \gets c_x$,直到 $id_x = 0$,开一个 `vector` 存储所有经过的飞机编号。对于 `vector` 中的每个数 $num$,把 $c_{num} \gets c_x$,这样下次查找时就会跳过没用的飞机。($\texttt{Upd}$:其实这就是并查集,此部分复杂度 $O\left( n\log n \right)$。当然你也可以同时使用路径压缩和按秩合并,复杂度几乎线性。) 假设我们已经分好组,如果你认为从中选择飞机数最多的 $n$ 个组即为答案,那就大错特错了。回顾算法流程,组与组之间其实有着时间上的先后。回到样例,第 $1,3,5$ 架为第一组,第 $2,4$ 架为第二组。若此时只有一个廊桥,则第二组是用不上的,因为飞机 $1$ 已经占用了!我们不用排序,直接处理出前 $i$ 个国内/国际航班的组的前缀和 $s_1,\,s_2$,答案为 $\max\left\{s_{1_i} + s_{2_{n - i}}\right\}$。 以下为考场代码。 ```cpp #include<cstdio> #include<iostream> #include<algorithm> #include<vector> using namespace std; const int N = 1e5; int n,m1,m2; struct Node{ int arr,lea,id; void read() { scanf("%d%d",&arr,&lea); return ; } bool operator < (const Node& x) const { return arr < x.arr; } }a[N + 5],b[N + 5]; int g1[N + 5],g2[N + 5],top1,top2;//g1 : 国内航班 g2 : 国际航班 int c[N + 5]; int nxta(int x)//二分+路径压缩 { int l = 1,r = m1,res = -1; while (l <= r) { int mid = (l + r) >> 1; if (a[mid].arr >= x) { res = mid; r = mid - 1; } else l = mid + 1; } if (res < 0) return res; vector<int> upd; while (res > 0 && a[res].id != 0) { upd.push_back(res); res = c[res]; } while (!upd.empty()) { c[upd.back()] = c[res]; upd.pop_back(); } return res; } void pre1() { for (int i = 1;i < m1;i++) c[i] = i + 1; c[m1] = -1;//-1表示没有飞机 for (int i = 1;i <= m1;i++) { if (a[i].id != 0) continue; a[i].id = 1; int cnt = 1,x = a[i].lea + 1; int pos = nxta(x); while (pos > 0) { a[pos].id = 1; x = a[pos].lea + 1; cnt++; pos = nxta(x); } top1++; g1[top1] = cnt; } return ; } int nxtb(int x) { int l = 1,r = m2,res = -1; while (l <= r) { int mid = (l + r) >> 1; if (b[mid].arr >= x) { res = mid; r = mid - 1; } else l = mid + 1; } if (res < 0) return res; vector<int> upd; while (res > 0 && b[res].id != 0) { upd.push_back(res); res = c[res]; } while (!upd.empty()) { c[upd.back()] = c[res]; upd.pop_back(); } return res; } void pre2() { for (int i = 1;i < m2;i++) c[i] = i + 1; c[m2] = -1; for (int i = 1;i <= m2;i++) { if (b[i].id != 0) continue; b[i].id = 1; int cnt = 1,x = b[i].lea + 1; int pos = nxtb(x); while (pos > 0) { b[pos].id = 1; x = b[pos].lea + 1; cnt++; pos = nxtb(x); } top2++; g2[top2] = cnt; } return ; } int s1[N + 5],s2[N + 5]; void calc() { for (int i = 1;i <= n;i++) { s1[i] = s1[i - 1] + g1[i]; } for (int i = 1;i <= n;i++) { s2[i] = s2[i - 1] + g2[i]; } return ; } int main() { freopen("airport.in","r",stdin); freopen("airport.out","w",stdout); scanf("%d%d%d",&n,&m1,&m2); for (int i = 1;i <= m1;i++) { a[i].read(); } for (int i = 1;i <= m2;i++) { b[i].read(); } sort(a + 1,a + m1 + 1); sort(b + 1,b + m2 + 1); pre1(); pre2(); calc(); int ans = 0; for (int i = 0;i <= n;i++) { ans = max(ans,s1[i] + s2[n - i]); } printf("%d\n",ans); return 0; } ```