首页 > 其他分享 >[NOI2016] 区间

[NOI2016] 区间

时间:2023-11-06 17:55:23浏览次数:30  
标签:10 le int text mid NOI2016 区间

[NOI2016] 区间

题目描述

在数轴上有 $n$ 个闭区间从 $1$ 至 $n$ 编号,第 $i$ 个闭区间为 $[l_i,r_i]$。

现在要从中选出 $m$ 个区间,使得这 $m$ 个区间共同包含至少一个位置。换句话说,就是使得存在一个 $x$ ,使得对于每一个被选中的区间 $[l_i,r_i]$,都有 $l_i \leq x \leq r_i$。

对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。

区间 $[l_i,r_i]$ 的长度定义为 $(r_i-l_i)$,即等于它的右端点的值减去左端点的值。

求所有合法方案中最小的花费。如果不存在合法的方案,输出 $-1$。

输入格式

第一行包含两个整数,分别代表 $n$ 和 $m$。

第 $2$ 到第 $(n + 1)$ 行,每行两个整数表示一个区间,第 $(i + 1)$ 行的整数 $l_i, r_i$ 分别代表第 $i$ 个区间的左右端点。

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1

6 3
3 5
1 2
3 4
2 2
1 5
1 4

样例输出 #1

2

提示

样例输入输出 1 解释

数据规模与约定

本题共 20 个测试点,各测试点信息如下表。

测试点编号 $ n= $ $ m= $ $ l_i,r_i $
1 $20$ $9$ $0 \le l_i \le r_i \le 100$
2 $20$ $10$ $0 \le l_i \le r_i \le 100$
3 $199$ $3$ $0 \le l_i \le r_i \le 100000$
4 $200$ $3$ $0 \le l_i \le r_i \le 100000$
5 $1000$ $2$ $0 \le l_i \le r_i \le 100000$
6 $2000$ $2$ $0 \le l_i \le r_i \le 100000$
7 $199$ $60$ $0 \le l_i \le r_i \le 5000$
8 $200$ $50$ $0 \le l_i \le r_i \le 5000$
9 $200$ $50$ $0 \le l_i \le r_i \le 10^9$
10 $1999$ $500$ $0 \le l_i \le r_i \le 5000$
11 $2000$ $400$ $0 \le l_i \le r_i \le 5000$
12 $2000$ $500$ $0 \le l_i \le r_i \le 10^9$
13 $30000$ $2000$ $0 \le l_i \le r_i \le 100000$
14 $40000$ $1000$ $0 \le l_i \le r_i \le 100000$
15 $50000$ $15000$ $0 \le l_i \le r_i \le 100000$
16 $100000$ $20000$ $0 \le l_i \le r_i \le 100000$
17 $200000$ $20000$ $0 \le l_i \le r_i \le 10^9$
18 $300000$ $50000$ $0 \le l_i \le r_i \le 10^9$
19 $400000$ $90000$ $0 \le l_i \le r_i \le 10^9$
20 $500000$ $200000$ $0 \le l_i \le r_i \le 10^9$

对于全部的测试点,保证 $1 \leq m \leq n$,$1 \leq n \leq 5 \times 10^5$,$1 \leq m \leq 2 \times 10^5$,$0 \leq l_i \leq r_i \leq 10^9$。

 

解题思路

  一开始想歪了,后面看题解发现可以二分就试了下,不过会超时。首先答案具有二段性,如果差值的最小值是 $\text{ans}$,那么当二分出差值为 $\text{mid}$,$\text{mid} \geq \text{ans}$,则至少存在一组合法方案,即某个点被 $m$ 个区间覆盖且 $m$ 个区间的差值不超过 $\text{mid}$($\text{ans}$ 对应的方案就是一组合法方案)。当 $\text{mid} < \text{ans}$ 则不存在任何合法方案。

  当二分出 $\text{mid}$ 后如何检查是否存在合法方案呢?对区间按照长度从小到大排序,然后依次枚举每个区间,维护一个指针 $j$,当枚举到第 $i$ 个区间时,如果 $len_{i} - len_{j} > \text{mid}$,则往右移动 $j$,直到差值不超过 $\text{mid}$,那么从 $j$ 到第 $i$ 就是要选择的区间。另外还需要用线段树来动态维护每个点被覆盖的次数,以及被覆盖最多次数是多少。如果在这个过程中发现某个位置被覆盖次数超过 $m$,则找到一个合法方案。

  TLE 代码如下,时间复杂度为 $O(\log{10^9} \cdot n \log{n})$:

#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;
}

  想一下能不能把二分的部分去掉。在检查的过程中本质是想知道是否存在差值不超过 $\text{mid}$ 的区间使得某个点被覆盖至少 $m$ 次。那么可以直接用双指针来维护,当枚举到第 $i$ 个区间,假设选择从 $i$ 到左边某个区间使得某个点被覆盖至少 $m$ 次,且距离 $i$ 最近的是 $j$,那么从 $j$ 到第 $i$ 就是要选择的区间,且以 $i$ 为右端点时差值是最小的。可以发现随着 $i$ 往右移动,$j$ 也之后往右移动,具有单调性,可以用双指针。

  其中如果想到固定所选区间的最大长度,然后找到在满足条件下的尽可能长的长度最小的区间,那么就会想到双指针。

  AC 代码如下,时间复杂度为 $O(n \log{n})$:

#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

标签:10,le,int,text,mid,NOI2016,区间
From: https://www.cnblogs.com/onlyblues/p/17813304.html

相关文章

  • 区间分组贪心
    是我见识少了,真没见过这种的……传送门如果看成有序排列的\((x,y)\)配对,那么可以写成\(r_x-l_y\)。(因为如果是负数,会在\(y,x\)的时候被枚举到,这样就不用考虑max和绝对值了)。于是,就是分成恰好长度为\(\frac{n}{2}\)的两组,一组贡献为\(r_i\),一组贡献为\(l_i\),求贡献最大值。假设......
  • cf1834E. MEX of LCM(维护右端点计算区间lcm)
    cf1834E首先可以估计一下答案的量级,因为小于答案的质数都要必须要出现,5e6以内的质数大概就是3e5,所以答案不超过5e6。我们维护以i右端点的lcm的值,这些值的数量不会太多,因为每次增长都至少×2,所以是log级别。每次新加的时候记得更新和去重即可。#include<cstdio>#include<algor......
  • 区间DP入门
    石子合并别人讲过太多了,蒟蒻就不说了。Polygon这题跟石子合并类似,只是多输出了个先清除哪条边可以使得值最大。因为我们不确定先删那一条,我们就再复制一遍添到输入的结尾,就变成了$2\timesN-1$。我们思考最大值是由哪些贡献的。最大值与最大值运算。最小值乘上最小值......
  • 字节笔试题-区间异或
    题目给两个长度为n的数组a,b,请你计算出有多少个区间[l,r],满足\(a_{l}\oplusa_{l+1}\oplusa_{l+2}\oplus\ldots\oplusa_{r}=b_{l}\oplusb_{l+2}\oplusb_{l+2}\oplus\ldots\oplusb_{r}\)(\(\oplus\)表示按位异或)输入描述第一行输入一个整数n,表示数组长度。第二行输......
  • 802. 区间和
    假定有一个无限长的数轴,数轴上每个坐标上的数都是 00。现在,我们首先进行 n� 次操作,每次操作将某一位置 x� 上的数加 c�。接下来,进行 m� 次询问,每个询问包含两个整数 l� 和 r�,你需要求出在区间 [l,r][�,�] 之间的所有数的和。输入格式第一行包含两个整数 n� 和 m�。接下来 n�......
  • NOIP[区间数据结构类问题]
    平面最近点对经典的分治问题,把所有的点按照\(x\)排序,然后分治处理两个子区间,然后枚举离中心少于已知最小值的点,判断能否出现更小值。intn,temp[250000];structnode{ intx,y;}a[500500];boolcmp(nodel,noder){ if(l.x==r.x)returnl.y<r.y; returnl.x<r.x;}doub......
  • 二次函数在区间上的最大(小)值问题
    前言本篇博文适合高一学生和高三一轮学习使用。对于高一学生而言,对初中学习的二次函数\(f(x)\)\(=\)\(ax^2\)\(+\)\(bx\)\(+\)\(c\)\(\quad\)\((a\neq0)\)已经形成了思维定势,总认为其最大值或者最小值是\(f(x)\)\(=\)\(f(-\cfrac{b}{2a})\)\(=\)\(\cfrac{4ac-b^2}{4a}\),很少......
  • ABC219 H 区间dp 费用提前计算
    ABC219H跟关路灯很像。很容易注意到我们拿走的只能是一个区间,观察n的范围发现区间dp是个好想法。朴素的想法是定义\(f_{i,j,k,0/1}\)为拿走i到j里面的所有数,走了k秒,现在在i/j的方案数。然后发现k太大了。咱当时的想法是希望优化复杂度,把k去掉结果发现不能保证正确性。......
  • 【洛谷 8649】 [蓝桥杯 2017 省 B] k 倍区间
    题目描述给定一个长度为 �N 的数列,�1,�2,⋯��A1​,A2​,⋯AN​,如果其中一段连续的子序列 ��,��+1,⋯��(�≤�)Ai​,Ai+1​,⋯Aj​(i≤j) 之和是 �K 的倍数,我们就称这个区间 [�,�][i,j] 是 �K 倍区间。你能求出数列中总共有多少个 �K 倍区间吗?输入格式第一行包含两个整数 �N 和 �K......
  • 习题专题-计算 某个闭区间 2出现的次数
    计算某个闭区间2出现的次数#include<stdio.h>intmain(){ inti=0; intL=0; intR=0; intcount=0; scanf("%d%d",&L,&R); for(i=L;i<=R;i++) { if(1<i&&i<9) { if(i%10==2) { count++; ......