[luogu p1712] 区间

Link\mathtt{Link}

传送门

Description\mathtt{Description}

给定 nn 个区间和一个整数 mm,选取若干区间让它们都包含 mm,且最小化选取的区间长度的极差。

Data Range & Restrictions\mathtt{Data} \text{ } \mathtt{Range} \text{ } \mathtt{\&} \text{ } \mathtt{Restrictions}

  • 1mn1 \le m \le n
  • 1n5×1051 \le n \le 5 \times 10^5
  • 1m2×1051 \le m \le 2 \times 10^5
  • 0liri1090 \le l_i \le r_i \le 10^9

Solution\mathtt{Solution}

首先将题目的要求分条来看:

  • 选取若干区间让它们包含 mm

  • 最小化选取的区间长度的极差

接下来是思路:

  • 为了统计在一段线段上,是否有一个点的覆盖次数达到 mm,不难考虑到可以用一颗线段树维护:修改区间加和(v=1v = 1),询问区间最大值。

  • 其次观察到“最小化选取区间长度极差”,再考虑到区间顺序不影响答案,nn 的范围同样也支持我们排序,因此我们考虑排序。因为排序后会有一个这样美好的性质:排序后,如果选中了第 ll 和第 rr 个线段,事实上 [l,r][l, r] 整个区间里的所有线段都可以考虑被选中因为并不会影响到极差,还可以让一个点的覆盖次数要不然不动,要不然增加。我可能不赚但一定不亏。

  • 那么这个时候其实就转化成了找连续的一段区间了。

  • 如果此时按照这样的算法来做,可以得到一个 O(n2logn)\operatorname{O}(n^2\log n) 的算法,也可以拿取一个不错的分数。接下来考虑进军正解:

  • logn\log n 并不可能优化掉了,那么我们真的需要 n2n^2 暴力枚举 llrr 吗?可不可以优化掉呢?这个时候自然联想到双指针。

  • 那么满不满足双指针的性质呢?首先抛开最优性,考虑可行性,可以发现,如果一个区间可行(覆盖次数超过 mm),那么完全包含该区间的所有区间都会满足有一个点覆盖次数超过 mm

  • 因此可以通过尺取法降为 nn,于是这个题我们就愉快地做完了。

一些细节:

  • 观察到 li,ril_i, r_i 很大,还发现我们只关心它们的大小关系,而不是具体的值,因此考虑离散化。

  • 但要注意,长度一定要在离散化前算好,而不能在离散化后重新更新。比如:1 3和3 6两条线段,离散化后变成1 2和2 3,我们期望的长度是原本的2和3,而不是后来的1和1(其实就是长度跟具体值有关)

  • 其次,离散化 uu 数组要开到 2n2n,因为每一个线段要保存左端点和右端点两个信息。

  • 最重要的一点:树状数组要开到 8n8n,因为实际上是 2n×42n \times 4

Time Complexity\mathtt{Time} \text{ } \mathtt{Complexity}

O(nlogn)\operatorname{O}(n\log n)

Code\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;
}

More\mathtt{More}

本题作为NOI的第一题其实有点水了,但是质量不输,是一道很好的线段树离散化练手题。

posted @   dbxxx  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示