架桥连接相邻岛
题目链接
https://codeforces.com/problemset/problem/555/B
题意
一维数轴上有若干个岛,岛屿岛之间不重叠,每个岛在数轴上有起点和终点。相邻两个岛上可以架桥,桥的两头要正好在岛上(不能多出去,但也要能架住)。现在给你一系列的桥,问能否把这些相邻的岛全部连起来。
数据范围:岛和桥的数量都\(\le2\times10^5\)
解题的(悲惨)心路历程
首先就想到了,应该把每个gap的min和max记录下来,然后开始猜想如何贪心 (因为是在王道上的贪心章节看到的题) 。然后就想到了把gap按min从大到小排序,按次序处理gap,每次找能满足当前(min最大)的gap的 可用的最长的bridge。。然后在脑海里成功证明了贪心选择性(开局能完成这个工作老兴奋了,后来blb尝试叉掉失败,我的分析是正确的,不愧是我):
反证法:如果当前可用的最长的bridge不用在这个min最大的gap上(即假设该局部解不是全局解),那么有两种可能(假设这种情况下,min最大的gap记做\(G_0\),此时放在\(G_0\)上的bridge是$ B'\(, 当前可用的最长的bridge为\)B_{max}\(。显然\)B'B_{max}$):
- 这个bridge在全局解中完全没有出现。那么,把$ B'\(换成\)B_{max}\(是可行的(因为根据假设,\)B_{max}\(放在\)G_0$上是可行的)
- 这个bridge在全局解中出现了,但是放在了另一个gap上(设为\(G'\))。根据前述gap的排序,\(G'.min\le G_0.min\),既然\(B'\)能放在\(G_0\)上,说明\(G'.min\le G_0.min\le B'\)。同理,既然\(B_{max}\)能放在\(G'\)上,说明\(B'\le B_{max}\le G'.max\)。综合起来就是\(G'.min\le B' \le G'.max\),因此\(B'\)可以放到\(G'\)上。又根据假设,\(B_{max}\)放在\(G_0\)上是可行的,所以\(B'\)和\(B_{max}\)可以交换位置,这样的解也是符合要求的。
根据上述分析,每次找能满足当前(min最大)的gap的 可用的最长的bridge,是某个可行解的一部分,按照这个贪心下去,一定能找到可行解。
主要工作已经完成了,屁颠屁颠写了个代码,大致是把gaps排序,bridges也排序,然后遍历每个gap,从前到后找可行bridge,找到就将其匹配,再另一个used数组上标记一下,表示这个bridge已经被用。算了一下复杂度是\(O(mn)\),侥幸交了几发,然后就T了。
于是痛定思痛,开始优化复杂度。想到bridges是有序的,想到二分。写了个二分,找小于当前gap.max的最大bridge。找到之后再往后移,直到有没用到的(used[l] = false),如果这个bridge\(\ge\)gap.min,就当做找到了;否则如果没找到或者bridge<gap.min就无解。这样写来是降了一点复杂度,多过了几个test,但是还是T了。原因应该是找used[l] = false花了不少时间。
大大小小调了很多东西,未果,扔给blb。得知C++中的set是平衡树,可用set来存bridges,利用其lower_bound方法实现上述的二分过程,同时返回的迭代器可直接用于erase方法。
遂尝试,结果WA了,仔细分析盲目分析数学分析xjb分析未果,遂扔给blb,出去遛弯。。。
后来blb发现了我的锅,在重载Bridge结构体的<的时候,只写了个长度的比较,没有把index加进去。由于set中元素的唯一性,导致了相同的Bridge就被替换掉了,遂顿悟。(此锅在后文中有详细总结)
题解
上面的贪心选择性已经证明了,这里直接给贪心思路:
把gaps以min为第一关键字,max为第二关键字,降序排序。依次处理每个gap,这样就相当于每次都在处理min最大的gap,记为\(G\)。现在需要找能放在\(G\)上的最大的bridge,只需找小于等于\(G.max\)最大的bridge就可,若找到的bridge的长度是\(\ge G.min\)的,就完成了这个gap的匹配,erase掉这个bridge即可;否则代表无解。
于是,把bridges放在set里,在找小于等于\(G.max\)最大的bridge时,就可以用到lower_bound方法。返回的迭代器可直接用于erase方法。
相似的问题
数轴上有n条线段,m个点,每个点最多可以分配给一个通过该点的线段,问是否每条线段都能被分配到一个符合要求的点,如何分配。
线段的起点终点就相当于本题的Gap的min和max,点的坐标就是各个bridge的长度。
需要记住的坑
C++中set是平衡树实现,能执行lower_bound(key)操作,意思是在set中找大于等于key的最小的元素,复杂度是\(log\)的。通过重载$ <$运算符能或自定义比较函数够实现相反的(找小于等于key的最小的元素)。
upper_bound是在set中找大于key的最小的元素
set不允许有两个同样的key值,所以在重载运算符的时候要注意。一定要保证 想加入进set的两个对象的值不能相等。
错误:
1
2
3
4
5
6
7
8struct Bridge {
ll l;
int index;
bool operator<(const Bridge& b) const {
return l > b.l;
}
};正确:
1
2
3
4
5
6
7
8struct Bridge {
ll l;
int index;
bool operator<(const Bridge& b) const {
return l > b.l || (l == b.l && index < b.index);
}
};重载上述<运算符的时候(包括在优先队列里也是),要给函数加const,否则会报错
(至于为什么,现在也不是很懂,以后再研究。。。。)
long long输出的格式符是%lld而不是%ld
AC代码
1 |
|