1. 程式人生 > >NOI2016區間bzoj4653(線段樹,尺取法,區間離散化)

NOI2016區間bzoj4653(線段樹,尺取法,區間離散化)

滿足 計算 方案 date cstring 先後 限制 一個點 答案

題目描述
在數軸上有 \(N\) 個閉區間 \([l_1,r_1],[l_2,r_2],...,[l_n,r_n]\) 。現在要從中選出 \(M\) 個區間,使得這 \(M\) 個區間共同包含至少一個位置。換句話說,就是使得存在一個 \(x\) ,使得對於每一個被選中的區間 \([l_i,r_i]\) ,都有 \(l_i≤x≤r_i\)

對於一個合法的選取方案,它的花費為被選中的最長區間長度減去被選中的最短區間長度。區間 \([l_i,r_i]\) 的長度定義為 \(r_i-l_i\) ,即等於它的右端點的值減去左端點的值。

求所有合法方案中最小的花費。如果不存在合法的方案,輸出 \(-1\)

輸入輸出格式
輸入格式:
第一行包含兩個正整數 \(N,M\) 用空格隔開,意義如上文所述。保證 \(1≤M≤N\)

接下來 \(N\) 行,每行表示一個區間,包含用空格隔開的兩個整數 \(l_i\)\(r_i\) 為該區間的左右端點。

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

輸出格式:
只有一行,包含一個正整數,即最小花費。

首先,這個區間的數值非常的大,我們需要離散化


可是我QwQ不會呀

區間離散化是將左端點,和右端點放到同一個數組裏,排序,然後依次賦值。
記得區間長度計算要在離散化之前

void init()
{
    for (int i=1;i<=n;i++)
    {
        b[++cnt].val=a[i].l;
        b[cnt].bel=1;b[cnt].num=i;
        b[++cnt].val=a[i].r;
        b[cnt].bel=2;b[cnt].num=i;
    }
    sort(b+1,b+1+cnt,cmp);
    for (int i=1;i<=cnt;i++)
    {
        if (b[i].val!=b[i-1].val) ymh++;
        if (b[i].bel==1) l[b[i].num]=ymh;
        else r[b[i].num]=ymh;
    }
    for (int i=1;i<=n;i++)
    {
        a[i].l=l[i];
        a[i].r=r[i];
    }
    //for (int i=1;i<=n;i++) printf("%d %d\n",a[i].l,a[i].r);
    sort(a+1,a+1+n,cmp1);
}

離散化初始化完之後呢~

我們考慮這個題是要求使得最大區間減去最小區間的值最小,那麽我們不妨按照區間長度排序

那麽,如果判斷一個點是否被覆蓋了m次呢。

我們可以直接對於每個\([l,r]\)將裏面的數都加一,然後統計區間最大值就可以了

那麽就需要一個線段樹了!


那.....之後呢QwQ,好像沒什麽用。

這時候就需要一個神奇的東西“尺取法”

尺取法:顧名思義,像尺子一樣取一段,借用挑戰書上面的話說,尺取法通常是對數組保存一對下標,即所選取的區間的左右端點,然後根據實際情況不斷地推進區間左右端點以得出答案。之所以需要掌握這個技巧,是因為尺取法比直接暴力枚舉區間效率高很多,尤其是數據量大的

時候,所以尺取法是一種高效的枚舉區間的方法,一般用於求取有一定限制的區間個數或最短的區間等等。當然任何技巧都存在其不足的地方,有些情況下尺取法不可行,無法得出正確答案

尺取法通常適用於選取區間有一定規律,或者說所選取的區間有一定的變化趨勢的情況,通俗地說,在對所選取區間進行判斷之後,我們可以明確如何進一步有方向地推進區間端點以求解滿足條件的區間,如果已經判斷了目前所選取的區間,但卻無法確定所要求解的區間如何進一步

得到根據其端點得到,那麽尺取法便是不可行的。首先,明確題目所需要求解的量之後,區間左右端點一般從最整個數組的起點開始,之後判斷區間是否符合條件在根據實際情況變化區間的端點求解答案。

舉個例子:

我們要求在給定的序列中求和等於x的區間的個數

那麽我們對於當前區間\([l,r]\),如果當前的和小於x,就\(r++\) ,否則\(l++\)

回到這道題

我們先按照長度從小到大依次加入區間,如果當然已經存在一個出現次數為\(m\)

那麽就更新答案,並跳\(l\),如果已知出現次數是m,那麽就一直更新答案了

否則就一直跳r

QwQ不過跳的時候邊界和更新與跳指針的先後要特別註意,詳細直接看代碼吧

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<vector>
#include<ctime>
using namespace std;

inline int read()
{
   int x=0,f=1;char ch=getchar();
   while (!isdigit(ch)){if (ch==‘-‘) f=-1;ch=getchar();}
   while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-‘0‘;ch=getchar();}
   return x*f;
}

const int maxn = 500010;

struct Node{
    int l,r,len;
};

struct pp{
    int val,num,bel;
};

Node a[maxn];
pp b[2*maxn];
int n,m;
int f[10*maxn];
int cnt;
int st,ed;
int add[10*maxn];
int l[maxn],r[maxn];

bool cmp(pp a,pp b)
{
    return a.val<b.val;
}

bool cmp1(Node a,Node b)
{
    return a.len<b.len;
}

int ymh=0;

void init()
{
    for (int i=1;i<=n;i++)
    {
        b[++cnt].val=a[i].l;
        b[cnt].bel=1;b[cnt].num=i;
        b[++cnt].val=a[i].r;
        b[cnt].bel=2;b[cnt].num=i;
    }
    sort(b+1,b+1+cnt,cmp);
    for (int i=1;i<=cnt;i++)
    {
        if (b[i].val!=b[i-1].val) ymh++;
        if (b[i].bel==1) l[b[i].num]=ymh;
        else r[b[i].num]=ymh;
    }
    for (int i=1;i<=n;i++)
    {
        a[i].l=l[i];
        a[i].r=r[i];
    }
    //for (int i=1;i<=n;i++) printf("%d %d\n",a[i].l,a[i].r);
    sort(a+1,a+1+n,cmp1);
}

void up(int root)
{
    f[root]=max(f[root<<1],f[root<<1|1]);
}

void pushdown(int root,int l,int r)
{
    if (add[root])
    {
        add[root<<1]+=add[root];
        add[root<<1|1]+=add[root];
        f[root<<1]=f[root<<1]+add[root];
        f[root<<1|1]=f[root<<1|1]+add[root];
        add[root]=0;
    }
}

void update(int root,int l,int r,int x,int y,int p)
{
    if (x<=l && r<=y)
    {
        f[root]=f[root]+p;
        add[root]+=p;
        return;
    }
    pushdown(root,l,r);
    int mid =(l+r) >> 1;
    if (x<=mid) update(root<<1,l,mid,x,y,p);
    if (y>mid) update(root<<1|1,mid+1,r,x,y,p);
    up(root);
}

int query(int root,int l,int r,int x,int y)
{
    if (x<=l && r<=y)
    {
        return f[root];
    }
    pushdown(root,l,r);
    int mid = (l+r) >> 1;
    int ans=0;
    if (x<=mid) ans=max(ans,query(root<<1,l,mid,x,y));
    if (y>mid) ans=max(ans,query(root<<1|1,mid+1,r,x,y));
    return ans; 
}

int ans=2e9;

int main()
{
  n=read(),m=read();
  for (int i=1;i<=n;i++)
  {
     a[i].l=read(),a[i].r=read();
     a[i].len=a[i].r-a[i].l;
  }
  init();
  st=1;ed=1;
  update(1,1,ymh,a[1].l,a[1].r,1);
  while (ed<n && st<n)
  {
    while (query(1,1,ymh,1,ymh)<m && ed<n && st<n) 
    {
      ++ed;
      update(1,1,ymh,a[ed].l,a[ed].r,1);
    }
    while(query(1,1,ymh,1,ymh)>=m && ed<n && st<n && st<ed) 
    {
        if (query(1,1,ymh,1,ymh)==m)
          ans=min(ans,a[ed].len-a[st].len);
        update(1,1,ymh,a[st].l,a[st].r,-1);
        ++st;
    }
  }
  if (ans==2e9) ans=-1;
  cout<<ans<<endl;
  return 0;
}

NOI2016區間bzoj4653(線段樹,尺取法,區間離散化)