zoukankan      html  css  js  c++  java
  • P1712 [NOI2016] 区间(双指针/线段树)

    题目描述

    在数轴上有 n个闭区间从 1 至 n 编号,第 i 个闭区间为 [li,ri][l**i,r**i] 。

    现在要从中选出 m 个区间,使得这 m 个区间共同包含至少一个位置。换句话说,就是使得存在一个 x ,使得对于每一个被选中的区间 [li,ri][l**i,r**i],都有 li≤x≤ri。

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

    区间 [li,ri][l**i,r**i] 的长度定义为 (ri−li) ,即等于它的右端点的值减去左端点的值。

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

    输入格式

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

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

    输出格式

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

    输入输出样例

    输入 #1复制

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

    输出 #1复制

    2
    

    看到题目说花费为“最长区间长度减去被选中的最短区间长度”,由此可以想到先给区间按照长度排序(和别的按照左右端点排序的题不一样)。然后考虑双指针法,右端点按照长度从小到大遍历,并把当前线段覆盖的区间整体+1,然后内循环只要有一个点的覆盖次数大于等于m,左指针就不断朝右指针移动更新答案,同时把指向的区间整体-1。可以用线段树来维护覆盖次数,需要维护的就是区间点值的最大值(查询的时候直接查询树根的值即可),看到数据范围记得离散化。

    证明:当右指针指向某一个区间时,左指针不断移动删除的区间必定不可能对答案有贡献(因为删掉以后仍然有点的被覆盖次数大于等于m,此时被删区间的下一个区间一定更优,这是由花费的计算方法决定的)。

    可能有人会问,这样得到的答案区间一定是从小到大排序后一段连续的区间吗(此区间是排序后线段下标构成的区间)?其实不一定,比如m = 3,而线段为(1, 2), (1, 3), (5, 6), (1, 10)。但其实我们并不关心这个,排序只是为了方便计算,最终得到的就是一个最大减最小值而已。

    #include <bits/stdc++.h>
    using namespace std;
    int n, m;
    struct Seg {
    	int l, r, len;
    } seg[500005];
    bool cmp(Seg a, Seg b) {
    	if(a.len != b.len) return a.len < b.len;
    	else return a.l < b.l;
    }
    struct SegmentTree {
    	int l, r, dat, mx, add;
    	#define l(x) tree[x].l
    	#define r(x) tree[x].r
    	#define add(x) tree[x].add
    	#define mx(x) tree[x].mx
    } tree[8000005];
    void build(int p, int l, int r) {
    	l(p) = l, r(p) = r;
    	if(l == r) {
    		mx(p) = 0;
    		return;
    	}
    	int mid = (l + r) >> 1;
    	build(2 * p, l, mid);
    	build(2 * p + 1, mid + 1, r);
    	mx(p) = 0;
    }
    void spread(int p) {
    	if(add(p)) {
    		add(2 * p) += add(p);
    		add(2 * p + 1) += add(p);
    		mx(2 * p) += add(p);
    		mx(2 * p + 1) += add(p);
    		add(p) = 0;
    	}
    }
    void change(int p, int l, int r, int d) {
    	//cout << p << endl;
    	if(l <= l(p) && r >= r(p)) {
    		add(p) += d;
    		mx(p) += d;
    		return;
    	}
    	spread(p);
    	int mid  = (l(p) + r(p)) / 2;
    	if(l <= mid) change(2 * p, l, r, d);
    	if(r > mid) change(2 * p + 1, l, r, d);
    	mx(p) = max(mx(2 * p), mx(2 * p + 1));
    }
    int main() {
    	cin >> n >> m;
    	int v[1000005], cnt = 0;
    	for(int i = 1; i <= n; i++) {
    		cin >> seg[i].l >> seg[i].r;
    		seg[i].len = seg[i].r - seg[i].l;
    		v[++cnt] = seg[i].r;
    		v[++cnt] = seg[i].l;
     	}
     	sort(seg + 1, seg + n + 1, cmp);
     	sort(v + 1, v + cnt + 1);
     	int len = unique(v + 1, v + cnt + 1) - v - 1;
    
     	for(int i = 1; i <= n; i++) {
     		seg[i].l = lower_bound(v + 1, v + len + 1, seg[i].l) - v;
     		seg[i].r = lower_bound(v + 1, v + len + 1, seg[i].r) - v;
     	}
     	int pos = 1, ans = 0x3f3f3f3f;
     	build(1, 1, 1000005);//别忘记建树  由于500000个线段,而线段是两个端点 因此要开1000000
     	for(int i = 1; i <= n; i++) {
     		change(1, seg[i].l, seg[i].r, 1);
     		while(tree[1].mx >= m && pos <= i) {
     			ans = min(ans, seg[i].len - seg[pos].len);
     			change(1, seg[pos].l, seg[pos].r, -1);
     			pos++;
     		}
     	}
     	if(ans != 0x3f3f3f3f) cout << ans;
     	else cout << -1;
    	return 0;
    }
    
  • 相关阅读:
    浅析堆与垃圾回收
    再探JVM内存模型
    索引使用的基本原则
    常见的索引模型浅析
    初识InnoDB体系架构和逻辑存储结构
    一条update SQL语句是如何执行的
    MySQL一条查询语句是如何执行的
    堆与优先队列
    ibatis BindingException Parameter 'status' not found. Available parameters are [arg1, arg0, param1, param2] 解决方法
    Mysql通过MHA实现高可用
  • 原文地址:https://www.cnblogs.com/lipoicyclic/p/14661393.html
Copyright © 2011-2022 走看看