[luogu p1712] 区间
\(\mathtt{Link}\)
\(\mathtt{Description}\)
给定 \(n\) 个区间和一个整数 \(m\),选取若干区间让它们都包含 \(m\),且最小化选取的区间长度的极差。
\(\mathtt{Data} \text{ } \mathtt{Range} \text{ } \mathtt{\&} \text{ } \mathtt{Restrictions}\)
- \(1 \le m \le n\)
- \(1 \le n \le 5 \times 10^5\)
- \(1 \le m \le 2 \times 10^5\)
- \(0 \le l_i \le r_i \le 10^9\)
\(\mathtt{Solution}\)
首先将题目的要求分条来看:
-
选取若干区间让它们包含 \(m\)
-
最小化选取的区间长度的极差
接下来是思路:
-
为了统计在一段线段上,是否有一个点的覆盖次数达到 \(m\),不难考虑到可以用一颗线段树维护:修改区间加和(\(v = 1\)),询问区间最大值。
-
其次观察到“最小化选取区间长度极差”,再考虑到区间顺序不影响答案,\(n\) 的范围同样也支持我们排序,因此我们考虑排序。因为排序后会有一个这样美好的性质:排序后,如果选中了第 \(l\) 和第 \(r\) 个线段,事实上 \([l, r]\) 整个区间里的所有线段都可以考虑被选中:因为并不会影响到极差,还可以让一个点的覆盖次数要不然不动,要不然增加。我可能不赚但一定不亏。
-
那么这个时候其实就转化成了找连续的一段区间了。
-
如果此时按照这样的算法来做,可以得到一个 \(\operatorname{O}(n^2\log n)\) 的算法,也可以拿取一个不错的分数。接下来考虑进军正解:
-
\(\log n\) 并不可能优化掉了,那么我们真的需要 \(n^2\) 暴力枚举 \(l\) 和 \(r\) 吗?可不可以优化掉呢?这个时候自然联想到双指针。
-
那么满不满足双指针的性质呢?首先抛开最优性,考虑可行性,可以发现,如果一个区间可行(覆盖次数超过 \(m\)),那么完全包含该区间的所有区间都会满足有一个点覆盖次数超过 \(m\)。
-
因此可以通过尺取法降为 \(n\),于是这个题我们就愉快地做完了。
一些细节:
-
观察到 \(l_i, r_i\) 很大,还发现我们只关心它们的大小关系,而不是具体的值,因此考虑离散化。
-
但要注意,长度一定要在离散化前算好,而不能在离散化后重新更新。比如:1 3和3 6两条线段,离散化后变成1 2和2 3,我们期望的长度是原本的2和3,而不是后来的1和1(其实就是长度跟具体值有关)
-
其次,离散化 \(u\) 数组要开到 \(2n\),因为每一个线段要保存左端点和右端点两个信息。
-
最重要的一点:树状数组要开到 \(8n\),因为实际上是 \(2n \times 4\)。
\(\mathtt{Time} \text{ } \mathtt{Complexity}\)
\(\operatorname{O}(n\log n)\)
\(\mathtt{Code}\)
/*
* @Author: crab-in-the-northeast
* @Date: 2022-03-25 23:20:07
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-03-26 00:03:35
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
flag = false;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if (flag)
return x;
return ~(x - 1);
}
const int maxn = 500005;
const int inf = 0x3f3f3f3f;
struct segment {
int l, r, len;
void set(int l, int r) {
this -> l = l;
this -> r = r;
this -> len = r - l;
}
const bool operator < (const segment b) {
return this -> len < b.len;
}
}a[maxn];
int u[maxn * 2];
int d[maxn << 3], t[maxn << 3];
inline int lson(int x) {
return x << 1;
}
inline int rson(int x) {
return x << 1 | 1;
}
inline void update(int x, int v) {
d[x] += v;
t[x] += v;
return ;
}
inline void pushdown(int x) {
if (t[x]) {
update(lson(x), t[x]);
update(rson(x), t[x]);
t[x] = 0;
}
return ;
}
inline void modify(int x, int l, int r, int L, int R, int v) {
if (L > r || R < l)
return ;
if (L <= l && R >= r) {
update(x, v);
return ;
}
pushdown(x);
int mid = l + r >> 1;
if (L <= mid)
modify(lson(x), l, mid, L, R, v);
if (R > mid)
modify(rson(x), mid + 1, r, L, R, v);
d[x] = std :: max(d[lson(x)], d[rson(x)]);
}
int main() {
int n = read(), m = read(), num = 0;
for (int i = 1; i <= n; ++i) {
int l = read(), r = read();
a[i].set(l, r);
u[++num] = l;
u[++num] = r;
}
std :: sort(a + 1, a + 1 + n);
std :: sort(u + 1, u + 1 + num);
num = std :: unique(u + 1, u + 1 + num) - u - 1;
for (int i = 1; i <= n; ++i) {
a[i].l = std :: lower_bound(u + 1, u + 1 + num, a[i].l) - u;
a[i].r = std :: lower_bound(u + 1, u + 1 + num, a[i].r) - u;
}
int ans = inf;
for (int i = 1, j = 1; i <= n; ++i) {
modify(1, 1, num, a[i].l, a[i].r, 1);
while(d[1] >= m) {
ans = std :: min(ans, a[i].len - a[j].len);
modify(1 ,1, num, a[j].l, a[j].r, -1);
++j;
}
}
if (ans == inf)
ans = -1;
printf("%d\n", ans);
return 0;
}
\(\mathtt{More}\)
本题作为NOI的第一题其实有点水了,但是质量不输,是一道很好的线段树离散化练手题。