[NOI2016]区间

尺取法 + 线段树优化

题目传送器


下面进入正题 :

     对前几个点 我们可以采用暴力枚举 选择区间的方案 暴力给每个点的权值+1 , 

时间复杂度为 O(2^n(m/2n+1) ) 应该有错

看起来这个算法很没用 也的确很没用

但每一个成功的算法都是有一个不成功的代码优化而来

这个算法也有很大的优化空间 :

  1. 我们可以看到 时间复杂度 中 “2^n” 是一个很难受的项

    我们首先考录消去它

    对此 , 我们可以采用尺取法 不知道尺取法的自行解决

    由于 每个区间对答案的贡献 仅与它的长度有关 , 与其他一切无关

    所以 , 我们可以 把每一个区间 按长度 进行排序

    这样的话 , 我们就可以进行 尺取法 了

    设 “尺” 的左端点 为 L ,右端点 为 R

    • L 和 R 最初 都为 1 (假设我们选了长度最小的区间)
    • R 不断 向右 扩展 , 每扩展一个区间 就把这个区间覆盖的点的点权+1,维护一个点权最大的点
    • 当 点权最大的点的点权>m 时 , 更新答案 L++
    • 尺取法 到 R>n 时 结束
        1. 暴力给 每一个点的点权+1 是很费时间的 猪都知道

          我们可以用线段树来优化

          线段树 在本题的优点 有两个方面:

             1. 加速 区间点权+1 ( 区间+ 操作)
             2. 维护 当前点权最大点 的 点权

          有了这线段树 , 时间复杂度 就能 再 下降一个档次

          成为 我们可以接受 的 复杂度

          #include<iostream>
          #include<cstdio>
          #include<algorithm>
          #include<cmath>
          #define MAXN 500005
          using namespace std;
          int n,m,cnt,v[3*MAXN];
          int ans=2100000000;
          struct node
          {
              int l,r,len;
          }f[MAXN];
          struct Tree
          {
              int l,r;
              int val;
              int add;
          }tree[10*MAXN];
          bool cmp(const node k,const node t)
          {
              return k.len<t.len;
          }
          void push(int now)
          {
             tree[now*2].add+=tree[now].add;
             tree[now*2].val+=tree[now].add;
             tree[now*2+1].val+=tree[now].add;
             tree[now*2+1].add+=tree[now].add;
             tree[now].add=0;
          }
          void build(int l,int r,int now)
          {
              tree[now].l=l;
              tree[now].r=r;
              tree[now].add=0;
              if (l==r)
              {
                  tree[now].val=0;
                  return;
              }
              int mid=(l+r)>>1;
              build(l,mid,now*2);
              build(mid+1,r,now*2+1);
              tree[now].val=max(tree[now*2].val,
                                    tree[now*2+1].val);
              return;
          }
          void change(int now,int l,int r,int k)
          {
              if(tree[now].l==l && tree[now].r==r)
              {
                  tree[now].add+=k;
                  tree[now].val+=k;
                  return;
              }
              push(now);
              int mid=(tree[now].l+tree[now].r)>>1;
              if (r<=mid) change(now*2,l,r,k);
              if (l>mid) change(now*2+1,l,r,k);
              if (l<=mid && r>mid)
              {
                  change(now*2,l,mid,k);                                change(now*2+1,mid+1,r,k);
              }
              tree[now].val=max(tree[now*2].val,
                                tree[now*2+1].val);
          }
          int main()
          {
              scanf("%d %d",&n,&m);
              for (int i=1;i<=n;i++)
              {
                  scanf("%d %d",&f[i].l,&f[i].r);
                  f[i].len=f[i].r-f[i].l+1;
                  v[++cnt]=f[i].l;
                  v[++cnt]=f[i].r;
              }
              sort(v+1,v+cnt+1);
              int x=unique(v+1,v+cnt+1)-(v+1);
              for (int i=1;i<=n;i++)
              {
                    f[i].l=lower_bound(v+1,v+x+1,f[i].l)-v;               
                f[i].r=lower_bound(v+1,v+x+1,f[i].r)-v; } build(1,x+100,1); sort(f+1,f+n+1,cmp); int now=1; for (int i=1;i<=n;i++) { change(1,f[i].l,f[i].r,1); while (tree[1].val>=m && now<=i) { ans=min(ans,f[i].len-f[now].len); change(1,f[now].l,f[now].r,-1); now++; } } if (ans!=2100000000) printf("%d\n",ans); else puts("-1"); return 0; }

           

posted @ 2019-11-22 20:09  鸟宿池边树  阅读(103)  评论(0编辑  收藏  举报