1. 程式人生 > >APIO2018 新家

APIO2018 新家

目錄

APIO2018 新家

題目傳送門

題意

五福街是一條筆直的道路,這條道路可以看成一個數軸,街上每個建築物的座標都可以用一個整數來表示。小明是一位時光旅行者,他知道在這條街上,在過去現在和未來共有 \(n\) 個商店出現。第 \(i\) 個商店可以使用四個整數 \(x_i\), \(t_i\), \(a_i\), \(b_i\)描述,它們分別表示:商店的座標、商店的型別、商店開業的年份、商店關閉的年份。

小明希望通過時光旅行,選擇一個合適的時間,住在五福街上的某個地方。他給出了一份他可能選擇的列表,上面包括了 \(q\)

個詢問,每個詢問用二元組(座標,時間)表示。第 \(i\) 對二元組用兩個整數 \(l_i\), \(y_i\)描述,分別表示選擇的地點 \(l_i\)和年份\(y_i\)

現在,他想計算出在這些時間和地點居住的生活質量。他定義居住的不方便指數為:在居住的年份,離居住點最遠的商店型別到居住點的距離。型別 \(t\) 的商店到居住點的距離定義為:在指定的年份,型別 \(t\) 的所有營業的商店中,到居住點距離最近的一家到居住點的距離。我們說編號為 \(i\) 的商店在第 \(y\) 年在營業當且僅當 \(a_i \leq y \leq b_i\)。注意,在某些年份中,可能在五福街上並非所有 \(k\)

種類型的商店都有至少一家在營業。在這種情況下,不方便指數定義為−1。你的任務是幫助小明求出每對(座標,時間)二元組居住的不方便指數。
\((1 \leq n,q \leq 3*10^5 , 1 \leq x_i,a_i,b_i \leq 10^9 , 1 \leq t_i \leq k , a_i \leq b_i , 1 \leq l_i,y_i \leq 10^8)\)

題解

首先很容易想到的是我們可以把一個商店在時間\([l,r]\)營業改為在時間\(l\)處插入和在時間\(r\)處刪除。這樣我們就有了一個大致的演算法:將所有的操作按照時間排序,然後順序地按照時間進行插入,並用某種資料結構動態維護貢獻,最後對於每組詢問輸出答案。
然後我們考慮如何去維護這個貢獻。首先無解的情況是比較好判的,我們就可以直接略過了。然後我們考慮這個答案的計算方法,實際上就是找一段最短的長度\(ans\)

,使得\([x-ans,x+ans]\)這段區間中涵蓋了所有的商店,這個顯然是可以二分答案來解決的。於是問題就轉化成為了二分答案之後查詢區間不同的數字是否等於\(k\)。然後繼續轉化問題,我們記錄每一個商店的前驅(即這一種商店上一次出現的位置)如果一種商店在區間\([l,r]\)中出現了,並且這種商店也在\([r+1,inf]\)中出現了,那麼這種商店在\([r+1,inf]\)這段區間中,每一個數的前驅必定\(\geq l\),如果在\([l,r]\)中沒有出現過,那麼在\([r+1,inf]\)區間裡面,前驅的最小值一定\(\leq l\)。知道了這個,我們相當於吧問題轉化成為了區間詢問前驅的最小值。這樣實際上我們就已經可以做掉這道題目了,開一個\(set\)維護每一個商店的前驅(為了方便,在0和inf的兩個位置都插入所有種類的商店),然後開一個線段樹維護前驅的最小值,每次二分答案之後線上段樹上\(Check\),總共的複雜度為\(O(nlog^2(n))\)
然後實際上這題還可以繼續優化,我們會發現我們在計算答案的時候,即用到了二分,又用到了線段樹。然而實際上,線段樹和二分往往是可以並在一起的(霧,然後我們就可以線上段樹上直接進行二分。原本二分的時候,我們如果即\([i,inf]\)這段區間的前驅最小值為\(Mn\),相當於是在找一個最大的位置\(i\),滿足\(Mn+i \leq 2*x\),所以我們直接線上段樹上二分即可,方法是這樣的:
假設當前區間為\([l,r]\)

  • 如果\(x\)\([mid+1,r]\)的範圍內,那麼說明\(i\)也一定在\([mid+1,r]\)範圍內,直接遞迴右子樹即可。
  • 如果\(x\)\([l,mid]\)的範圍內,那麼\(i\)在左右兩個區間都有可能存在,然後判斷當前的\(mid+1\)是否滿足\(Mn+i \leq 2*x\)的條件,如果滿足則往右子樹遞迴,否則往左子樹遞迴。
    這樣做的複雜度就是\(O(nlog(n))\)的了。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+500;
const int Inf=1e9+7;
int n,nq,tot,c,sptot,k;
int srt[N<<2],cnt[N<<1];
multiset<int>S[N<<1];
typedef multiset<int>::iterator Sit;
struct Opt {
  int opt;
  int x,t,tp,idx,ans;
  bool operator < (const Opt &rhs) const {
    return t==rhs.t?opt<rhs.opt:t<rhs.t;
  }
}p[N<<2];
bool Cmp(Opt a,Opt b) {
  return a.idx<b.idx;
}

struct Set {
  priority_queue<int,vector<int>,greater<int> >Q,Del;
  int Top() {
    while(!Del.empty()&&Q.top()==Del.top()) {
      Q.pop();Del.pop();
    }
    return Q.empty()?c+1:Q.top();
  }
}q[N<<1];

namespace Seg {
  int Mn[N<<4],pos[N<<4];
#define ls(o) o<<1
#define rs(o) o<<1|1
  void Build(int o,int l,int r) {
    Mn[o]=Inf;
    if(l==r) {
      pos[o]=srt[l];
      return ;
    }
    int mid=(l+r)>>1;
    Build(ls(o),l,mid);Build(rs(o),mid+1,r);
    pos[o]=pos[ls(o)];
  }
  void Modify(int o,int l,int r,int ps,int v) {
    if(l==r) {
      Mn[o]=srt[v];
      return ;
    }
    int mid=(l+r)>>1;
    if(ps<=mid) Modify(ls(o),l,mid,ps,v);
    else Modify(rs(o),mid+1,r,ps,v);
    Mn[o]=min(Mn[ls(o)],Mn[rs(o)]);
    return ;
  }
  int Query(int o,int l,int r,int ps) {
    if(l==r) return Mn[o];
    int mid=(l+r)>>1;
    if(ps>mid) return Query(rs(o),mid+1,r,ps);
    else return min(Mn[rs(o)],Query(ls(o),l,mid,ps));
  }
  int Ask(int ps) {
    ps=srt[ps];
    int l=0,r=c+1;
    int o=1;
    while(l<r) {
      int mid=(l+r)>>1;
      if(ps>=pos[rs(o)]) o=rs(o),l=mid+1;
      else {
        if(pos[rs(o)]+Mn[rs(o)]+1<=2*ps) o=rs(o),l=mid+1;
        else o=ls(o),r=mid;
      }
    }
    int Min=Query(1,0,c+1,l+1);
    int ret=max(srt[l],2*ps-Min);
    return ret-ps;
  }
}

void Init() {
  c=0;
  for(int i=1;i<=tot;i++) {
    srt[++c]=p[i].x;
  }
  sort(srt+1,srt+1+c);
  c=unique(srt+1,srt+1+c)-srt-1;
  srt[0]=-Inf,srt[c+1]=Inf;
  for(int i=1;i<=tot;i++) {
    p[i].x=lower_bound(srt+1,srt+1+c,p[i].x)-srt;
  }
  sort(p+1,p+1+tot);
  for(int i=1;i<=k;i++) {
    S[i].insert(0);
    S[i].insert(c+1);
    q[c+1].Q.push(0);
  }
  Seg::Build(1,0,c+1);
  Seg::Modify(1,0,c+1,c+1,0);
}

void Insert(int ps,int tp) {
  cnt[tp]++;sptot+=cnt[tp]==1;
  Sit it=S[tp].insert(ps);
  Sit nw=it;it--;
  q[ps].Q.push(*it);
  Seg::Modify(1,0,c+1,ps,q[ps].Top());
  q[*(++nw)].Del.push(*it);
  q[*(nw)].Q.push(ps);
  Seg::Modify(1,0,c+1,*(nw),q[*nw].Top());
  return ;
}

void Delete(int ps,int tp) {
  cnt[tp]--;sptot-=cnt[tp]==0;
  Sit it=S[tp].find(ps);
  Sit tmp=it,nw=it;it--;
  q[ps].Del.push(*it);
  Seg::Modify(1,0,c+1,ps,q[ps].Top());
  q[*(++nw)].Del.push(*tmp);
  q[*nw].Q.push(*it);
  Seg::Modify(1,0,c+1,*(nw),q[*nw].Top());
  S[tp].erase(tmp);
}

int main() {
  scanf("%d%d%d",&n,&k,&nq);
  for(int i=1,x,t,a,b;i<=n;i++) {
    scanf("%d%d%d%d",&x,&t,&a,&b);
    p[++tot].opt=-1;p[tot].x=x;p[tot].tp=t;p[tot].t=a;p[tot].idx=Inf;
    p[++tot].opt=1;p[tot].x=x;p[tot].tp=t;p[tot].t=b;p[tot].idx=Inf;
  }
  for(int i=1,l,y;i<=nq;i++) {
    scanf("%d%d",&l,&y);
    p[++tot].opt=0;p[tot].x=l;p[tot].t=y;p[tot].idx=i;
  }
  Init();
  for(int i=1;i<=tot;i++) {
    if(p[i].opt==-1) Insert(p[i].x,p[i].tp);
    else if(p[i].opt==0) {
      if(sptot!=k) p[i].ans=-1;
      else p[i].ans=Seg::Ask(p[i].x);
    }
    else Delete(p[i].x,p[i].tp);
  }
  sort(p+1,p+1+tot,Cmp);
  for(int i=1;i<=nq;i++) {
    printf("%d\n",p[i].ans);
  }
  return 0;
}