[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的第一题其实有点水了,但是质量不输,是一道很好的线段树离散化练手题。

posted @ 2022-03-26 00:58  dbxxx  阅读(38)  评论(0编辑  收藏  举报