算法
(贪心) $O(nlogn)$
我们先介绍做法,然后给出证明。
算法流程:
- 将所有奶牛按照 minSPF 从大到小的顺序排序,然后依次考虑每头奶牛;
- 对于每头奶牛,扫描当前所有能用的防晒霜,选择 SPF 值最大的防晒霜来用;
证明:
将所有奶牛和防晒霜分别看成两个集合中的点,如果一头奶牛可以使用某个防晒霜,则在两个点之间连一条边,那么问题就变成二分图求最大匹配了;
根据匈牙利算法的原理,如果一个匹配不存在增广路径,则该匹配是二分图的一个最大匹配。下面我们来证明按照上述做法得到的匹配方案,不存在增广路径。
反证,假设存在增广路径,我们来看其中最短的一条。为了方便,我们用 $c_i$ 表示第 $i$ 头牛,用 $s_i$ 表示第 $i$ 个防晒霜。
假设我们选择的最短增广路径是 $s_{i_0} \rightarrow c_{i_1} \rightarrow s_{i_2} \rightarrow c_{i_3} \rightarrow … \rightarrow c_{i_k}$,则 $s_{i_0}$ 和 $c_{i_k}$ 均是不在匹配方案中的点。
我们考虑增广路径的起点 $s_{i_0}$ 可以发现:
- $s_{i_0} \leq s_{i_2}$,这是因为我们会给每头奶牛匹配一个尽可能大的防晒霜;
- $i_1 > i_3$,否则,我们可以将 $c_{i_1}$ 和 $s_{i_2}$ 从增广路径中删除,和最短增广路径矛盾;
同理可以依次考虑 $s_{i_2}, s_{i_4} … $,可以发现如下性质:
- $s_{i_j} \leq s_{i_{j + 2}}$;
- $i_j > i_{j + 2}$;
所以 $i_{k - 2} > i_k$,所以在上述做法中,我们会先枚举到 $c_{i_k}$,然后再枚举 $c_{i_{k - 2}}$,与 $c_{i_k}$ 没有匹配任何防晒霜矛盾,所以上述做法一定不存在增广路径,所以上述做法得到的匹配一定是最大匹配。
时间复杂度分析
做法第一步用快速排序即可,时间复杂度是 $O(nlogn)$。
做法第二步可以用 STL 中的 map 来做,核心操作是找小于等于某个数的最大的数是多少,可以借助于 upper_bound
这个函数,每次操作的时间复杂度是 $O(logn)$,总时间复杂度是 $O(nlogn)$。
C++ 代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
typedef pair<int,int> PII;
const int N = 2510;
int n, m;
PII cows[N];
int main()
{
cin >> n >> m;
map<int, int> spfs;
for (int i = 0; i < n; i ++ ) cin >> cows[i].first >> cows[i].second;
for (int i = 0; i < m; i ++ )
{
int spf, cover;
cin >> spf >> cover;
spfs[spf] += cover; // 注意这里要写 +=,因为数据中存在spf值相同的防晒霜
}
sort(cows, cows + n);
int res = 0;
spfs[0] = spfs[1001] = n;
for (int i = n - 1; i >= 0; i -- )
{
auto spf = spfs.upper_bound(cows[i].second);
spf --;
if (spf->first >= cows[i].first)
{
res ++ ;
if (--spf->second == 0)
spfs.erase(spf);
}
}
cout << res << endl;
return 0;
}
😊😊😊😊😊😊😊😊😊✔✔✔✔✔✔✔✔✔👀👀👀👀👀👀👀👀👀😃😃😃😃😃😃😃😃🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️ヾ(≧▽≦)oヾ(≧▽≦)oヾ(≧▽≦)oヾ(≧▽≦)oヾ(≧▽≦)oヾ(≧▽≦)oヾ(≧▽≦*)o℃¥£′㎜㏎㏄¤℉$₩″㎝㎎㏕₠°€¢㎡㎞㎏○₡₢₥₨₭₰£₣₦₪₮₱¢≈=≌<≯[>﹙﹚﹛~∽﹢﹟‱%&×±﹜≠≡﹦≤>≦]﹤﹥﹡/‰^#-≒≥≮^
?
❤❤❤❤❤❤❤❤❤❤❤
🎶🎶🎶🎶🎶🎶🎶🎶🎶🎶🎶🎶
$i_1 > i_3$,否则,我们可以将 $c_{i1}$ 和 $s_{i2}$ 从增广路径中删除,和最短增广路径矛盾;
$\rightarrow$ 反证,假设 $i_1 < i_3$,
∴ $maxSPF[c_{i3}] \geqslant SPF[s_{i2}]$ ,同时 $minSPF[c_{i1}] \leqslant SPF[s_{i0}] \leqslant SPF[s_{i2}]$
∴ $minSPF[c_{i3}] \leqslant minSPF[c_{i1}]$
∴
(综上画图可得)$minSPF[c_{i3}] \leqslant SPF[s_{i0}] \leqslant maxSPF[c_{i3}]$,因此我们可以将 $c_{i1}$ 和 $s_{i2}$ 从增广路径中删除,连接 $s_{i0}$ 和 $c_{i3}$,从而得到更短的增广路径,和上述的最短增广路径矛盾为什么一定要降序,不能是升序
这里哨兵为什仫要设两个?只设置一个哨兵为什仫会出错?
能不能建图求二分图最大匹配?qwq
不能,复杂度高超了,匈牙利(nm)
能啊,dinic直接a了
为什么不可以从小到大排minspf,然后每次选择最小的spf呐
应该也是可以的,两种方法是对称的。
肯定是从小到大排$maxSPF$, 然后每次选择最小的$SPF$
啊对滴,之前手误了,从小到大排序的话要排$maxSPF$,然后每次选最小的$SPF$
其实从小到大排minspf,每次选择maxspf最小的(用堆维护)也是对的qwq,贪心思路大体上差不多,但严格证明就不知了qwq,code
解决了我的疑惑!谢谢!
funfact:精细实现可以做到O(n)
# TQL
强强强
这里为什么要设置哨兵?
这样写也可以:
求助大佬
迭代器是什么?
语法问题还是百度比较好。
您好大大,请问二分图为什么会超时呢?复杂度不是O(nm)吗?
最坏情况下 $m$ 是 $n^2$ 级别的,那么总时间复杂度就是 $n^3$ 了。
懂了,谢谢!
大佬,我这个代码哪里出了问题了呀
这个做法和上面证明的算法是反着的,因此要完全对称过来。只需要把排序改成以区间右端点为第一关键字即可。
明白了,谢谢大佬
这里设置哨兵有什么作用啊 hhh
auto spf = spfs.upper_bound(cows[i].second);
这句话是在spfs
中查找大于cows[i].second
的最小元素,设置哨兵是为了让这条语句不返回空。tql Orz
SPF: Sun Protection Factor
从右往左:
if (spf->first >= cows[i].first && spf->first <= cows[i].second) 这行可以简化成
if (spf->first >= cows[i].first)
对滴~
从左往右:
棒!按右端点从小到大排序,从左往右扫描也是可以的
但是这个好像只能过部分用例