[NOI2016] 区间
[NOI2016] 区间
题目描述
在数轴上有 个闭区间从 至 编号,第 个闭区间为 。
现在要从中选出 个区间,使得这 个区间共同包含至少一个位置。换句话说,就是使得存在一个 ,使得对于每一个被选中的区间 ,都有 。
对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。
区间 的长度定义为 ,即等于它的右端点的值减去左端点的值。
求所有合法方案中最小的花费。如果不存在合法的方案,输出 。
输入格式
第一行包含两个整数,分别代表 和 。
第 到第 行,每行两个整数表示一个区间,第 行的整数 分别代表第 个区间的左右端点。
输出格式
输出一行一个整数表示答案。
样例 #1
样例输入 #1
6 3
3 5
1 2
3 4
2 2
1 5
1 4
样例输出 #1
2
提示
样例输入输出 1 解释
数据规模与约定
本题共 20 个测试点,各测试点信息如下表。
测试点编号 | |||
1 | |||
2 | |||
3 | |||
4 | |||
5 | |||
6 | |||
7 | |||
8 | |||
9 | |||
10 | |||
11 | |||
12 | |||
13 | |||
14 | |||
15 | |||
16 | |||
17 | |||
18 | |||
19 | |||
20 |
对于全部的测试点,保证 ,,,。
解题思路
一开始想歪了,后面看题解发现可以二分就试了下,不过会超时。首先答案具有二段性,如果差值的最小值是 ,那么当二分出差值为 ,,则至少存在一组合法方案,即某个点被 个区间覆盖且 个区间的差值不超过 ( 对应的方案就是一组合法方案)。当 则不存在任何合法方案。
当二分出 后如何检查是否存在合法方案呢?对区间按照长度从小到大排序,然后依次枚举每个区间,维护一个指针 ,当枚举到第 个区间时,如果 ,则往右移动 ,直到差值不超过 ,那么从 到第 就是要选择的区间。另外还需要用线段树来动态维护每个点被覆盖的次数,以及被覆盖最多次数是多少。如果在这个过程中发现某个位置被覆盖次数超过 ,则找到一个合法方案。
TLE 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10, M = N * 2;
int n, m;
int l[N], r[N], p[N];
int xs[M], sz;
struct Node {
int l, r, mx, add;
}tr[M * 4];
void build(int u, int l, int r) {
if (l == r) {
tr[u] = {l, r, 0, 0};
}
else {
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tr[u] = {l, r, 0, 0};
}
}
void pushdown(int u) {
if (tr[u].add) {
tr[u << 1].mx += tr[u].add;
tr[u << 1].add += tr[u].add;
tr[u << 1 | 1].mx += tr[u].add;
tr[u << 1 | 1].add += tr[u].add;
tr[u].add = 0;
}
}
void modify(int u, int l, int r, int c) {
if (tr[u].l >= l && tr[u].r <= r) {
tr[u].mx += c;
tr[u].add += c;
}
else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, c);
if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
}
}
int find(int x) {
int l = 1, r = sz;
while (l < r) {
int mid = l + r >> 1;
if (xs[mid] >= x) r = mid;
else l = mid + 1;
}
return l;
}
bool check(int mid) {
build(1, 1, sz);
for (int i = 1, j = 1; i <= n; i++) {
modify(1, find(l[p[i]]), find(r[p[i]]), 1);
while (r[p[j]] - l[p[j]] + mid < r[p[i]] - l[p[i]]) {
modify(1, find(l[p[j]]), find(r[p[j]]), -1);
j++;
}
if (tr[1].mx >= m) return true;
}
return false;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d %d", l + i, r + i);
xs[++sz] = l[i];
xs[++sz] = r[i];
p[i] = i;
}
sort(xs + 1, xs + sz + 1);
sz = unique(xs + 1, xs + sz + 1) - xs - 1;
sort(p + 1, p + n + 1, [&](int i, int j) {
return r[i] - l[i] < r[j] - l[j];
});
int l = 0, r = 1e9 + 1;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
if (l > 1e9) l = -1;
printf("%d", l);
return 0;
}
想一下能不能把二分的部分去掉。在检查的过程中本质是想知道是否存在差值不超过 的区间使得某个点被覆盖至少 次。那么可以直接用双指针来维护,当枚举到第 个区间,假设选择从 到左边某个区间使得某个点被覆盖至少 次,且距离 最近的是 ,那么从 到第 就是要选择的区间,且以 为右端点时差值是最小的。可以发现随着 往右移动, 也之后往右移动,具有单调性,可以用双指针。
其中如果想到固定所选区间的最大长度,然后找到在满足条件下的尽可能长的长度最小的区间,那么就会想到双指针。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10, M = N * 2;
int n, m;
int l[N], r[N], p[N];
int xs[M], sz;
struct Node {
int l, r, mx, add;
}tr[M * 4];
void build(int u, int l, int r) {
if (l == r) {
tr[u] = {l, r, 0, 0};
}
else {
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tr[u] = {l, r, 0, 0};
}
}
void pushdown(int u) {
if (tr[u].add) {
tr[u << 1].mx += tr[u].add;
tr[u << 1].add += tr[u].add;
tr[u << 1 | 1].mx += tr[u].add;
tr[u << 1 | 1].add += tr[u].add;
tr[u].add = 0;
}
}
void modify(int u, int l, int r, int c) {
if (tr[u].l >= l && tr[u].r <= r) {
tr[u].mx += c;
tr[u].add += c;
}
else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, c);
if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
}
}
int find(int x) {
int l = 1, r = sz;
while (l < r) {
int mid = l + r >> 1;
if (xs[mid] >= x) r = mid;
else l = mid + 1;
}
return l;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d %d", l + i, r + i);
xs[++sz] = l[i];
xs[++sz] = r[i];
p[i] = i;
}
sort(xs + 1, xs + sz + 1);
sz = unique(xs + 1, xs + sz + 1) - xs - 1;
sort(p + 1, p + n + 1, [&](int i, int j) {
return r[i] - l[i] < r[j] - l[j];
});
build(1, 1, sz);
int ret = 2e9;
for (int i = 1, j = 1; i <= n; i++) {
modify(1, find(l[p[i]]), find(r[p[i]]), 1);
while (tr[1].mx >= m) {
modify(1, find(l[p[j]]), find(r[p[j]]), -1);
if (tr[1].mx < m) {
modify(1, find(l[p[j]]), find(r[p[j]]), 1);
break;
}
j++;
}
if (tr[1].mx == m) ret = min(ret, r[p[i]] - l[p[i]] - (r[p[j]] - l[p[j]]));
}
if (ret == 2e9) ret = -1;
printf("%d", ret);
return 0;
}
参考资料
题解 P1712 【[NOI2016]区间】:https://www.luogu.com.cn/blog/zykykyk/solution-p1712
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17813304.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战