【BZOJ4653】[Noi2016]区间 双指针法+线段树

【BZOJ4653】[Noi2016]区间

Description

在数轴上有 n个闭区间 [l1,r1],[l2,r2],...,[ln,rn]。现在要从中选出 m 个区间,使得这 m个区间共同包含至少一个位置。换句话说,就是使得存在一个 x,使得对于每一个被选中的区间 [li,ri],都有 li≤x≤ri。

对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。区间 [li,ri] 的长度定义为 ri−li,即等于它的右端点的值减去左端点的值。

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

Input

第一行包含两个正整数 n,m用空格隔开,意义如上文所述。保证 1≤m≤n

接下来 n行,每行表示一个区间,包含用空格隔开的两个整数 li 和 ri 为该区间的左右端点。

N<=500000,M<=200000,0≤li≤ri≤10^9

Output

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

Sample Input

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

Sample Output

2

题解:这不是我最喜爱的(没有之一)双指针法吗?然而GXZ没等我看题就告诉我正解简直丧心病狂~

因为总代价只和最长区间和最短区间有关,我们将区间按长度排序,那么中间的区间都可以免费取。我们采用双指针法,枚举右端点r,再不断右移l直到[l,r]中的区间刚好满足条件。是否满足条件可以用线段树判定,只需要再每次平移指针的时候维护一下线段树就行了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define lson x<<1
#define rson x<<1|1
using namespace std;
const int maxn=500010;
int n,m,nm,ans;
int s[maxn<<4],t[maxn<<4];
struct node
{
	int len,a,b;
}p[maxn];
struct number
{
	int val,org,k;
}num[maxn<<1];
bool cmp1(node a,node b)
{
	return a.len<b.len;
}
bool cmp2(number a,number b)
{
	return a.val<b.val;
}
int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+gc-‘0‘,gc=getchar();
	return ret*f;
}
void updata(int l,int r,int x,int a,int b,int v)
{
	if(a<=l&&r<=b)
	{
		s[x]+=v,t[x]+=v;
		return ;
	}
	if(t[x])	s[lson]+=t[x],s[rson]+=t[x],t[lson]+=t[x],t[rson]+=t[x],t[x]=0;
	int mid=l+r>>1;
	if(a<=mid)	updata(l,mid,lson,a,b,v);
	if(b>mid)	updata(mid+1,r,rson,a,b,v);
	s[x]=max(s[lson],s[rson]);
}
int main()
{
	n=rd(),m=rd();
	int i,j;
	for(i=1;i<=n;i++)
		num[i].val=rd(),num[i+n].val=rd(),num[i].org=num[i+n].org=i,num[i+n].k=1,p[i].len=num[i+n].val-num[i].val;
	sort(num+1,num+2*n+1,cmp2);
	num[i-1].val=-1;
	for(i=1;i<=2*n;i++)
	{
		if(num[i].val>num[i-1].val)	nm++;
		if(num[i].k)	p[num[i].org].b=nm;
		else	p[num[i].org].a=nm;
	}
	sort(p+1,p+n+1,cmp1);
	ans=1<<30;
	for(i=1;i<=n&&s[1]<m;updata(1,nm,1,p[i].a,p[i].b,1),i++);
	if(i>n&&s[1]<m)
	{
		printf("-1");
		return 0;
	}
	i--,updata(1,nm,1,p[i].a,p[i].b,-1);
	for(j=1;i<=n;i++)
	{
		for(updata(1,nm,1,p[i].a,p[i].b,1);j<=i&&s[1]>=m;updata(1,nm,1,p[j].a,p[j].b,-1),j++);
		j--,updata(1,nm,1,p[j].a,p[j].b,1);
		ans=min(ans,p[i].len-p[j].len);
	}
	printf("%d",ans);
	return 0;
}
//6 3 3 5 1 2 3 4 2 2 1 5 1 4
时间: 2024-10-22 13:45:41

【BZOJ4653】[Noi2016]区间 双指针法+线段树的相关文章

FOJ 2171 防守阵地 II 区间求和区间查询 线段树

题目链接:http://acm.fzu.edu.cn/problem.php?pid=2171 题意: 给定n长序列,常数m,q个询问 对于每个询问x 1.求[x, x+m-1] 区间和 2.[x,x+m-1]区间的所有元素-1 线段树裸题,不知为何全用longlong会re,只能改成部分longlong #include<stdio.h> #include<string.h> #define ll long long #define LL int #define L(x) (x*

[BZOJ4653][Noi2016]区间

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

线段树———区间最大数(线段树入门)

线段树初级(区间最大数) 其实就是对树进行二分查找      (当然需要结合递归) 思路: 要从区间中找到最大数,当然可以暴力求解,但你不怕超时吗??? so      让我们来学习线段树吧!!!!!!!!!!!!!!! 在c++里下面这个代码是极快的(哇咔咔!!!) 题目描述 给出一列数共N个,将其从1到N编号,进行M次查询[X, Y](X<=Y),给出第X个数到第Y个数间最大的数. 输入 一组测试数据,第一行输入N,M(1<=N, M<=10^5),第二行N个数:之后M行,每行分别为

线段树之区间最大数(线段树入门)

线段树初级(区间最大数) 其实就是对树进行二分查找      (当然需要结合递归) 思路: 要从区间中找到最大数,当然可以暴力求解,但你不怕超时吗??? so      让我们来学习线段树吧!!!!!!!!!!!!!!! #include <iostream> #include <cstdio> #include <cstring> using namespace std; #define maxn 100010 struct N {     int l, r, max

【BZOJ3878】【Ahoi2014】奇怪的计算器 维护区间性质。线段树

广告: #include <stdio.h> int main() { puts("转载请注明出处[vmurder]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/44037685"); } 题解: 先排序然后插入线段树 用线段树每次对全区间进行操作. 然后维护哪些段区间溢出了,对这段区间进行赋值. 溢出处理: 一个区间的左端点大于最大值,或者右端点小于最小值 那么这个区间就该被覆盖. 覆盖,加特

Benelux Algorithm Programming Contest 2014 Final ACM-ICPC Asia Training League 暑假第一阶段第二场 E. Excellent Engineers-单点更新、区间最值-线段树 G. Growling Gears I. Interesting Integers-类似斐波那契数列-递推思维题

先写这几道题,比赛的时候有事就只签了个到. E. Excellent Engineers 传送门: 这个题的意思就是如果一个人的r1,r2,r3中的某一个比已存在的人中的小,就把这个人添加到名单中. 因为是3个变量,所以按其中一个变量进行sort排序,然后,剩下的两个变量,一个当位置pos,一个当值val,通过线段树的单点更新和区间最值操作,就可以把名单确定. 代码: 1 //E-线段树 2 #include<iostream> 3 #include<cstdio> 4 #incl

HDU 3308 LCIS (经典区间合并)【线段树】

<题目链接> 题目大意: 给你一段序列,对其进行两种操作,一是修改某个序号的点的值:二是查询某个区间的LCIS(最长上升子序列). 解题分析: 线段树区间合并的典型例题,用求某个区间的LCIS时,需要比较三个值,一是左区间的LCIS,二是右区间的LCIS,三是左右子区间合并的LCIS.最重要的是第三点如何实现,实现第三点需要维护一个最长后缀上升子序列和最长前缀上升子序列. #include <cstdio> #include <cstring> #include <

HDU 1540 Tunnel Warfare(区间合并)【线段树】

<题目链接> 题目大意: 题意:一个长度为n的线段,下面m个操作 D x 表示将单元x毁掉 R  表示修复最后毁坏的那个单元 Q x  询问这个单元以及它周围有多少个连续的单元,如果它本身已经被毁坏了就是0. 解题分析: 用线段树求指定点所在的最长连续区间,属于线段树区间合并类型的题,线段树的每个节点需要维护三个值,分别是对应区间的最长连续区间长度,对应区间最长连续区间前缀,对应区间最长连续后缀,然后就是在每次update之后都维护一下这三个值就行.并且注意一下query 时的操作. 1 #i

HDU 4553 约会安排 (区间合并)【线段树】

<题目链接> 寒假来了,又到了小明和女神们约会的季节.  小明虽为屌丝级码农,但非常活跃,女神们常常在小明网上的大段发言后热情回复"呵呵",所以,小明的最爱就是和女神们约会.与此同时,也有很多基友找他开黑,由于数量实在过于巨大,怎么安排时间便成了小明的一大心事.  我们已知小明一共有T的空闲时间,期间会有很多女神或者基友来找小明.  作为一个操作系统曾经怒考71分的大神,小明想到了一个算法,即"首次适应算法",根据操作系统课本的描述,就是找一段最靠前的符