1. 题目来源
前导题:[并查集] aw3115. 疯狂的馒头(经典并查集+离线询问+离线算法+模板)
2. 题目解析
本次周赛的 2 3 4 题质量都相当高。
本次第四题方法很多,线段树、树状数组、multiset、并查集等都可以做。
采用并查集解法,一定先去掌握前导题 前导题:[并查集] aw3115. 疯狂的馒头(经典并查集+离线询问+离线算法+模板),这是一个非常经典的并查集模型,必须掌握。
注意:
摘自,加了一点自己的理解:
作者:daydayUppp
链接:https://leetcode-cn.com/problems/minimum-interval-to-include-each-query/solution/chi-san-hua-chi-xian-si-wei-bing-cha-ji-w2osk/
来源:力扣(LeetCode)
- 离散化。 本题数据范围很大,数据量很少,需要离散化。
n
的范围是1e7
的数量级,所以对每一段区间去更新每一个点一定会超时。所以换一个角度,只对每一段区间的端点和要查询的点考虑则数量级就到了1e5
的数量级 即使nlogn
的时间复杂度也可以通过,所以把 区间端点 和 每个查询 的点,加入一个数组,并排序这样就把最多1e7
个点 离散成1e5
个点。但其实这个解法是可以处理10^7
的数据的,力扣太慢了,10^7
会超时。例比 疯狂的馒头。 - 离线思维。 因为把每一个区间都事先给出,所以可以事先利用区间信息来处理数据,实现优化因为由题目可以看出区间长度越短优先度越高,即: 先更新优先度高的区间,再更新优先度低的,这样不会重复更新,保证最优答案。
- 并查集。 思路和疯狂的馒头一模一样,没啥好说的。
离散化用的少…掌握这个思维和处理方式。
- 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),主要在排序的时间上,并查集在此没有按秩合并,所以理论时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn) 的,但实际时间复杂度可以媲美 O ( 1 ) O(1) O(1)。
- 空间复杂度: O ( n ) O(n) O(n)
并查集+离散化:
class Solution {
public:
vector<int> alls, p, w; // 离散化、并查集、颜色
// 在此并查集没有按秩合并,仅有路径压缩,时间复杂度为 O(logn),但是一般情况下为 O(1)
// 得到 x 右边第一个未被染色的点
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 离散化得到下标
int get(int x) {
return lower_bound(alls.begin(), alls.end(), x) - alls.begin();
}
vector<int> minInterval(vector<vector<int>>& intervals, vector<int>& queries) {
// 值域 1e7,数量 1e5,故可以离散化区间端点和查询区间长度
for (auto s : intervals) alls.push_back(s[0]), alls.push_back(s[1]);
for (auto x : queries) alls.push_back(x);
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
int n = alls.size();
// 多初始化一个元素,因为最后一个元素被染色之后会指向下一个元素,故 n+1 号元素也会被用到
p.resize(n + 1), w.resize(n + 1, -1);
for (int i = 0; i < n + 1; i ++ ) p[i] = i; // 初始化并查集
// 按区间长度从小到大排序,离线算法:将所有询问按照特定的顺序重排序,再依次进行处理
sort(intervals.begin(), intervals.end(), [](vector<int> &a, vector<int> &b) {
return a[1] - a[0] < b[1] - b[0];
});
// 区间长度越小优先级越高,按区间长度从小到大将区间每个点覆盖一遍并染上颜色,保证了区间长度最短,最优覆盖
for (auto it : intervals) {
int l = get(it[0]), r = get(it[1]), len = it[1] - it[0] + 1; // 得到离散化后的左右端点,得到区间长度
while (find(l) <= r) { // l 的祖先存的是下一个未被染色的点,若仍在该区间内,则对其进行染色
l = find(l); // 该点的祖先存的是下一个没有染色的点,即更新这个左端点
p[l] = l + 1; // p[l]存l的右边第一个未被染色的点。此时l将被染成 len,让p[l]=l+1,达到将该点删除的目的
w[l] = len;
}
}
// 离线求结果,和 1847 最近的房间,同周双周赛的第4题一样,同为离线算法
vector<int> res;
for (auto x : queries) res.push_back(w[get(x)]);
return res;
}
};