zoukankan      html  css  js  c++  java
  • NOI2016 区间 【线段树】

    题目

    在数轴上有 n个闭区间 [l1,r1],[l2,r2],...,[ln,rn]。现在要从中选出 m 个区间,使得这 m个区间共同包含至少一个位置。换句话说,就是使得存在一个 x,使得对于每一个被选中的区间 [li,ri],都有 li≤x≤ri。
    对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。区间 [li,ri] 的长度定义为 ri−li,即等于它的右端点的值减去左端点的值。
    求所有合法方案中最小的花费。如果不存在合法的方案,输出 −1。

    输入格式

    第一行包含两个正整数 n,m用空格隔开,意义如上文所述。保证 1≤m≤n
    接下来 n行,每行表示一个区间,包含用空格隔开的两个整数 li 和 ri 为该区间的左右端点。
    N<=500000,M<=200000,0≤li≤ri≤10^9

    输出格式

    只有一行,包含一个正整数,即最小花费。

    输入样例

    6 3

    3 5

    1 2

    3 4

    2 2

    1 5

    1 4

    输出样例

    2

    题解

    比较容易想到,如果我们对所选的区间都+1,那么一定存在某个位置的值>=m
    先对区间离散化
    这样我们可以得到一个(O(n^3))的算法

    区间修改求最值,离散化后用线段树优化,可以做到(O(n^2logn))

    为了进一步减小一个n,我们将区间按区间长度排序
    从小开始逐个加入线段树中,直到出现>=m的位置,再尝试从小开始从线段树中删除,直至满足>=m时再移动右端点

    可以证明这样做的正确性
    因为区间过多不影响存在某位置>=m,通过不断移动右端点,有解一定可以判出
    设当前解为len,如果不移动左端点,右端点移动的结果一定不会比len要小
    所以在满足条件的情况下应尽量移动左端点
    (O(nlogn))

    #include<iostream>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long int
    #define REP(i,n) for (int i = 1; i <= (n); i++)
    #define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
    #define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
    #define ls (u << 1)
    #define rs (u << 1 | 1)
    using namespace std;
    const int maxn = 1000005,maxm = 500005,INF = 1000000000;
    inline int read(){
    	int out = 0,flag = 1; char c = getchar();
    	while (c < 48 || c > 57) {if (c == '-') flag = -1; c = getchar();}
    	while (c >= 48 && c <= 57) {out = (out << 3) + (out << 1) + c - '0'; c = getchar();}
    	return out * flag;
    }
    struct node{int l,r;}e[maxm];
    int b[maxn],bi,tot = 1,n,m,ans = INF;
    int mx[4 * maxn],tag[4 * maxn];
    inline bool operator <(const node& a,const node& x){
    	return (b[a.r] - b[a.l]) < (b[x.r] - b[x.l]);
    }
    int getn(int x){return lower_bound(b + 1,b + 1 + tot,x) - b;}
    void pd(int u){
    	if (tag[u]){
    		mx[ls] += tag[u]; mx[rs] += tag[u];
    		tag[ls] += tag[u]; tag[rs] += tag[u];
    		tag[u] = 0;
    	}
    }
    void modify(int u,int l,int r,int L,int R,int x){
    	if (l >= L && r <= R){
    		mx[u] += x; tag[u] += x;
    		return;
    	}
    	pd(u);
    	int mid = l + r >> 1;
    	if (mid >= L) modify(ls,l,mid,L,R,x);
    	if (mid < R) modify(rs,mid + 1,r,L,R,x);
    	mx[u] = max(mx[ls],mx[rs]);
    }
    int main(){
    	n = read(); m = read();
    	for (int i = 1; i <= n; i++)
    		b[++bi] = e[i].l = read(),b[++bi] = e[i].r = read();
    	sort(b + 1,b + 1 + bi);
    	for (int i = 2; i <= bi; i++) if (b[i] != b[tot]) b[++tot] = b[i];
    	for (int i = 1; i <= n; i++)
    		e[i].l = getn(e[i].l),e[i].r = getn(e[i].r);
    	sort(e + 1,e + 1 + n);
    	modify(1,1,tot,e[1].l,e[1].r,1);
    	int l = 1,r = 1,L,R;
    	L = R = b[e[1].r] - b[e[1].l];
    	if (mx[1] >= m) ans = min(ans,R - L);
    	while (r <= n){
    		while (mx[1] >= m && l < r){
    			modify(1,1,tot,e[l].l,e[l].r,-1);
    			l++;
    			L = b[e[l].r] - b[e[l].l];
    			if (mx[1] >= m) ans = min(ans,R - L);
    		}
    		r++;
    		if (r > n) break;
    		R = b[e[r].r] - b[e[r].l];
    		modify(1,1,tot,e[r].l,e[r].r,1);
    		if (mx[1] >= m) ans = min(ans,R - L);
    	}
    	printf("%d
    ",ans == INF ? -1 : ans);
    	return 0;
    }
    
    
  • 相关阅读:
    ThinkPHP 实现数据库事务回滚示例代码
    Java数据结构和算法
    Java数据结构和算法
    Java数据结构和算法
    git push每次提交都要输入用户名的解决方案
    Java数据结构与算法
    Java数据结构和算法
    Java数据结构和算法
    类和接口
    git如何忽略文件
  • 原文地址:https://www.cnblogs.com/Mychael/p/8465241.html
Copyright © 2011-2022 走看看