[NOI2016]区间

Posted wlzs1432

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[NOI2016]区间相关的知识,希望对你有一定的参考价值。

题目大意:

给你N个区间,选出M个,
使这m个区间共同包含至少一个位置,且使里面区间长度最长减    
区间长度最短最小

solution:

线段树 + 尺取法

首先很容易想到对于这个区间覆盖了,就是让区间中的每个点加1,那就是线段树,维护区间最大值

那么这时候就有一个问题:就是区间末尾可能很大。

这个时候就要用到离散化:

显然这道题只关心区间的长度,和区间之间的相互包含关系。因此可以预先把长度记录下来,把出现过的数按数值排序。 注意重复的数。。。

    for(int i=1;i<=n;i++)
    {
        l[i]=read(),r[i]=read();
        s[i].len=r[i]-l[i];s[i].num=i;//记录长度 
        ++cnt;
        b[cnt].num=i;b[cnt].val=l[i];b[cnt].bz=0;
        ++cnt;
        b[cnt].num=i;b[cnt].val=r[i];b[cnt].bz=1;
    }
    sort(b+1,b+cnt+1);//离散化
    int q=0;
    for(int i=1;i<=cnt;i++)
    {
        if(b[i].val!=b[i-1].val) q++;//如果有重复得要                                      赋成一个值
        if(b[i].bz==0)
        l[b[i].num]=q;
        else
        r[b[i].num]=q;
    }
    for(int i=1;i<=n;i++)
    {
        s[i].l=l[s[i].num];
        s[i].r=r[s[i].num];
    }//最后代回去

 

离散化完了,接下来怎么做? 这个时候如果当前的最大值小于了M,显然我们需要多加区间,而已经大于了,我们可以尝试减少区间来实现更有的解。 显然这和尺取法的思想一样,即选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况时,步步推进,放缩。。。

然后发现我们要维护最小长度,显然就按长度排序,再尺取法。。 减区间时线段树维护区间-1,加区间时维护区间+1,最后直接查询树顶。。。

先上代码

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 500000 +10;

const int inf = 1<<30;

inline long long read()
{
    int f=1,x=0;
    char ch;
    do
    {
        ch=getchar();
        if(ch==-) f=-1;
    }while(ch<0||ch>9);
    do
    {
        x=(x<<1)+(x<<3)+ch-0;
        ch=getchar();
    }while(ch>=0&&ch<=9);
    return f*x;
}

inline int Max(int a,int b)
{
    if(a>=b) return a;
    else return b; 
}

int n,m;

int l[MAXN],r[MAXN];

struct stick
{
    int len;
    int l;
    int r;
    int num;
    friend bool operator < (stick a1,stick a2)
    {
        return a1.len<a2.len;
    }
}s[MAXN*4]; 

struct node
{
    int num;
    int val;
    bool bz;
    friend bool operator < (node a1,node a2)
    {
        return a1.val<a2.val;
    }
};

int cnt =0;

node b[MAXN*4];

struct stree
{
    int l;
    int r;
    int maxv;
    inline int mid()
    {
        return (l+r)>>1;
    }
}tree[MAXN * 8];

#define lc o<<1
#define rc o<<1|1 

inline void build(int o,int l,int r)
{
    tree[o].l=l,tree[o].r=r;
    if(l==r) tree[o].maxv=0;
    else
    {
        int mid=tree[o].mid();
        build(lc,l,mid);
        build(rc,mid+1,r);
        tree[o].maxv=Max(tree[lc].maxv,tree[rc].maxv);
        return;
    }
} 

int add[MAXN * 8];

inline void pushup(int o)
{
    add[lc]+=add[o];
    add[rc]+=add[o];
    tree[lc].maxv+=add[o];
    tree[rc].maxv+=add[o];
    add[o]=0;
}

inline void update(int o,int x,int y,int w)
{
    int l=tree[o].l,r=tree[o].r;
    if(r<x||l>y) return;
    if(x<=l&&y>=r)
    {
        add[o]+=w;
        tree[o].maxv+=w;
        return;
    }
    else
    {
        pushup(o);
        update(lc,x,y,w);
        update(rc,x,y,w);
        tree[o].maxv=Max(tree[lc].maxv,tree[rc].maxv);
    }
}

int ans=1<<30;

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        l[i]=read(),r[i]=read();
        s[i].len=r[i]-l[i];s[i].num=i; 
        ++cnt;
        b[cnt].num=i;b[cnt].val=l[i];b[cnt].bz=0;
        ++cnt;
        b[cnt].num=i;b[cnt].val=r[i];b[cnt].bz=1;
    }
    sort(b+1,b+cnt+1);
    int q=0;
    for(int i=1;i<=cnt;i++)
    {
        if(b[i].val!=b[i-1].val) q++;
        if(b[i].bz==0)
        l[b[i].num]=q;
        else
        r[b[i].num]=q;
    }
    for(int i=1;i<=n;i++)
    {
        s[i].l=l[s[i].num];
        s[i].r=r[s[i].num];
    }
    build(1,1,q);
    sort(s+1,s+n+1);
    int li=0,ri=0;
    while(1)
    {
        while(tree[1].maxv<m&&ri<=n)
        {
            ri++; 
            update(1,s[ri].l,s[ri].r,1);
        }
        if(tree[1].maxv<m) break;
        while(tree[1].maxv>=m&&li<=ri)
        {
            li++; 
            update(1,s[li].l,s[li].r,-1);

        } 
            ans=min(ans,s[ri].len-s[li].len);
    }
    if(ans==inf)
    {
        printf("-1");
        return 0;
    }
    printf("%d\n",ans);
}

 

注意

1.第一次写的时候把建树的一个L写成了1... 然后全RE,然后非常智障的认为是数组开小了,然后非常智障的又交了几次。。。 2.第二个错是离散化后忘了这一步。。。

    for(int i=1;i<=n;i++)
    {
        s[i].l=l[s[i].num];
        s[i].r=r[s[i].num];
    }

 

3.有一次在考试时做过这道题,当时很naive的写的纯模拟,因为根本没想到用线段树,然后模拟加树状数组拿了50分,其他人都拿了60分QwQ 模拟代码

// luogu-judger-enable-o2
#include<bits/stdc++.h>

using namespace std;

const int inf = 1<<30;

int n,m;

struct node
{
    int l,r;
    inline friend bool operator < (node a,node b)
    {
        return a.l<b.l;
        }
};node t[100000+ 10];

inline int read()
{
    int f=1,x=0;
    char ch;
    do
    {
        ch=getchar();
        if(ch==-) f=-1;
    }while(ch<0||ch>9);
    do
    {

        x=(x<<3)+(x<<1)+ch-0;
        ch=getchar();
    }while(ch>=0&&ch<=9);
    return f*x;
}

int minn,maxn;
int ans=inf;

int b[500010];

int c[500001];
int lb(int x){return x&-x;};
inline void change(int a,int b)
{
    if(a)
    { 
    while(a<=maxn)
    {
        c[a]+=b;
        a+=lb(a);
    }
    }
}
inline int g_sum(int x)
{
    int re=0;
    while(x>=1)
    {
        re+=c[x];
        x-=lb(x);
    }
    return re;
}

bool boo;

int main()
{
    n=read();
    minn=inf;
    m=read();
    for(int i=1;i<=n;i++)
    {
        t[i].l=read(),t[i].r=read();
        minn=min(minn,t[i].l);
        maxn=max(maxn,t[i].r);
        change(t[i].l,1);
        change(t[i].r+1,-1);
    }
    sort(t+1,t+n+1);
    for(register int i=minn;i<=maxn;i++)
    {
        if(g_sum(i)>=m)
        {
            boo=true;
        int num=0;
        int maxx=0,minx=inf;
        for(register int j=1;j<=n;j++)
        {
            if(i>=t[j].l&&i<=t[j].r)
            {
                num++;
            //  cout<<b[num]<<endl;
                b[num]=t[j].r-t[j].l;
            }
            if(t[j].l>i) break;
        }

            sort(b+1,b+num+1);
            int xiao = inf;
            for(register int i=1;i<=num-m+1;i++)
            {
                if(b[i+m-1]-b[i]<xiao)
                    xiao=b[i+m-1]-b[i];
            }
            ans=min(ans,xiao);
        /*  for(int i=1;i<=num;i++)
            {
                cout<<b[i]<<" "<<endl;
            }*/

    }
    }
    if(ans!=inf)
    cout<<ans<<endl;
    else cout<<-1<<endl;
}

 

以上是关于[NOI2016]区间的主要内容,如果未能解决你的问题,请参考以下文章

[Noi2016]区间

AC日记——NOI2016区间 bzoj 4653

[BZOJ4653][Noi2016]区间

bzoj 4653: [Noi2016]区间

[NOI2016]区间

BZOJ4653: [Noi2016]区间