NOI2016区间

8 篇文章 0 订阅
1 篇文章 0 订阅

传送门

题解

Step 1

首先想如果知道了是哪些区间
怎么判断使得这 m个区间共同包含至少一个位置

方法:区间打标记

每次给一个区间,就把这个区间的值+1
如果有一个地方超过了m次,显然m个区间至少共同包含一个位置
这个时候就需要一个可以支持区间修改,查询区间最大值的数据结构

那么就是线段树

Step 2

如何知道选那些区间呢?

首先对于每个区间 [L,R] 可以算出它的权值 = R-L
要求最大权值减去最小权值最小
可以先对区间进行排序
如样例排序为

权值
220
121
341
352
143
154

这样,如果找到一个指针指着上面的一列,一个指针指着下面的某一列,这其中的区间满足Step1的条件,就可以更新ans=min(ans,权值[下]-权值[上])

Step 3

具体实现?

维护一个L指针,一个R指针,R指针一直向下
如果L到R的区间满足max>=m的条件
就看看能不能让L指针下移,一直更新
每次R++以后就把这个R在的区间的值+1
每次L++以后就把L在的这个区间的值-1

线段树维护区间最大值就行了

Step 4

线段树开不下那么多点,怎么办?

直接裸线段树可以打到70分
其他的RE
这个时候就需要离散化了
所谓离散化就是把很多分散的点映射到有限的空间里面去
如100 10999999 123
变成 1 3 2
不离散化要开f[11000000]
但离散化只用开f[3]
这样可以大大节省空间复杂度

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ls 2*p
#define rs 2*p+1 
#define inf 0x7fffffff
using namespace std;
int n,m;
int pos[5000005];
struct node{
    int addv,max;
    int l,r;
}a[5000010];
struct data{
    int l,r;
    int w;
    data(int x=0,int y=0){
        l=x;r=y;w=y-x;
    }   
}interval[5000005];
bool cmp(data xx,data yy){
    return xx.w<yy.w;
}
void build(int l,int r,int p){
    a[p].l=l;a[p].r=r;
    if(l==r) return ;
    build(l,(l+r)/2,2*p);
    build((l+r)/2+1,r,2*p+1);
}
void pushdown(int p){
    if(a[p].addv!=0){
        a[ls].addv+=a[p].addv;
        a[ls].max+=a[p].addv;
        a[rs].addv+=a[p].addv;
        a[rs].max+=a[p].addv;
        a[p].addv=0;
    }
}
void pushup(int p){
    a[p].max=max(a[ls].max,a[rs].max);
}
void update(int p,int l,int r,int k){ 
    if(l>r) return;
    if(a[p].l==l&&a[p].r==r){a[p].max+=k;a[p].addv+=k;return;}
    pushdown(p);
    int mid=(a[p].l+a[p].r)/2;
    if(r<=mid) update(ls,l,r,k);
    else if(l>mid)  update(rs,l,r,k);
    else{
        update(ls,l,mid,k);
        update(rs,mid+1,r,k);
    }
    pushup(p);
} 
bool check(){
    return a[1].max>=m;
}
int main(){
    //printf("size=%d\n",sizeof(a)+sizeof(interval)); 
    scanf("%d%d",&n,&m);
    int x,y;
    int maxr=-inf,minl=inf;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&pos[i*2-1],&pos[i*2]);
        interval[i]=data(pos[i*2-1],pos[i*2]);
    }
    sort(pos+1,pos+2*n+1);
    int tot=0;
    for(int i=1;i<=2*n;i++){
        while(pos[i]==pos[i+1])i++;
        pos[++tot]=pos[i];
    }
    for(int i=1;i<=n;i++){
        interval[i].l=lower_bound(pos+1,pos+1+tot,interval[i].l)-pos;
        interval[i].r=lower_bound(pos+1,pos+1+tot,interval[i].r)-pos;
        maxr=max(maxr,interval[i].r);minl=min(minl,interval[i].l);
    }
    build(minl,maxr,1);
    sort(interval+1,interval+1+n,cmp);
    int L=1,R=1;
    int ans=inf;
    for(R=1;R<=n&&L<=n;R++){
        update(1,interval[R].l,interval[R].r,1);
        while(check()){
            update(1,interval[L].l,interval[L].r,-1);
            ans=min(ans,interval[R].w-interval[L].w);   L++;
            //printf("L=%d:R=%d %d,ans=%d\n",L,R,interval[R].w-interval[L].w,ans);
        }
    }
    if(ans==inf) printf("-1\n");
    else printf("%d",ans);
    return 0;   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值