1. 程式人生 > WINDOWS開發 >題解 洛谷 P4632 【[APIO2018] New Home 新家】

題解 洛谷 P4632 【[APIO2018] New Home 新家】

首先考慮可以用二分答案來解決詢問,可以二分一個長度\(len\),若在區間\([x-len,x+len]\)內包含了所有\(k\)種的商店,那麼這個\(len\)就是合法的,可以通過二分來求其最小值。

對每個商店的存在時間轉化為在\(a\)時刻出現,在\(b+1\)時刻消失,然後和詢問一起離線按時間排序,就可以解決時間這一維的限制了。

然後考慮如何快速查詢區間內是否包含所有的商店,和支援維護商店的出現消失。

對於這種區間數顏色的問題,可以對每個位置記錄與其商店型別相同的上一個位置\(pre\),發現一個位置上可能會有多個商店,那麼這裡的\(pre\)改為記錄這些商店的到其各自商店型別相同的上一個位置的最小值。

\(pre\)是記錄該位置商店型別相同的上一個位置,所以對於區間\([l,r]\),如果從\(r+1\)往後的所有位置的\(pre\)的最小值小於\(l\),那麼說明至少有一種商店沒在該區間出現。但是\(r+1\)往後可能並不會包含所有\(k\)種商店,因此加入哨兵商店來避免討論,分別在最前面和最後面加入每種商店各一個。

然後就是如何支援維護\(pre\),對於每個位置開一個\(multiset\)維護該位置所有商店的對應其商店型別的前驅,\(multiset\)中的最小值即為該位置的\(pre\),然後用線段樹動態開點來維護區間\(pre\)的最小值,這裡其實就是線上段樹的每個葉子節點開了一個\(multiset\)

來維護資訊。

對於商店的出現消失維護,對每種商店型別開一個\(multiset\),維護該型別所有商店的出現位置,然後出現和消失只用解決對於該位置同類型的前驅和後繼的影響就行,線段樹單點修改即可實現。

若用線段樹查詢最小值來判定二分,複雜度是\(O(n\ log^2\ n)\)的,可以直接線上段樹上二分位置,複雜度就是\(O(n\ log\ n)\)的了。

細節挺多,具體實現就看程式碼吧。

\(code:\)

#include<bits/stdc++.h>
#define maxn 900010
#define all 200000000
#define mid ((l+r)>>1)
using namespace std;
typedef multiset<int>::iterator muli;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c==‘-‘)flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int n,k,q,tot,root,tree_cnt,num;
int mi[maxn*20],ls[maxn*20],rs[maxn*20],ans[maxn];
multiset<int> p[maxn],s[maxn*20];
struct node
{
    int pos,tim,id,opt;
}t[maxn];
bool cmp(const node &a,const node &b)
{
    if(a.tim==b.tim) return a.opt<b.opt;
    return a.tim<b.tim;
}
void modify(int l,int r,int pos,int v,int type,int &cur)
{
    if(!cur) cur=++tree_cnt;
    if(l==r)
    {
        if(type) s[cur].insert(v);
        else s[cur].erase(s[cur].find(v));
        if(!s[cur].empty()) mi[cur]=*s[cur].begin();
        else mi[cur]=all;
        return;
    }
    if(pos<=mid) modify(l,mid,pos,v,type,ls[cur]);
    else modify(mid+1,r,rs[cur]);
    mi[cur]=min(mi[ls[cur]],mi[rs[cur]]);
}
int query(int pos)
{
    if(num<k) return -1;
    int l=1,r=all,cur=root,midmi,rmi=all;
    while(l<r)
    {
        midmi=min(rmi,mi[rs[cur]]);
        if(pos>mid||midmi<2*pos-mid) cur=rs[cur],l=mid+1;
        else rmi=midmi,cur=ls[cur],r=mid;
    }
    return l-pos;
}
int main()
{
    read(n),read(k),read(q),mi[0]=all;
    for(int i=1;i<=k;++i)
    {
        p[i].insert(-all),p[i].insert(all);
        modify(1,all,-all,1,root);
    }
    for(int i=1;i<=n;++i)
    {
        int x,a,b;
        read(x),read(id),read(a),read(b);
        t[++tot]=(node){x,1};
        t[++tot]=(node){x,b+1,0};
    }
    for(int i=1;i<=q;++i)
    {
        int pos,tim;
        read(pos),read(tim);
        t[++tot]=(node){pos,i,2};
    }
    sort(t+1,t+tot+1,cmp);
    for(int i=1;i<=tot;++i)
    {
        int opt=t[i].opt,id=t[i].id,pos=t[i].pos;
        muli a,b;
        if(opt==0)
        {
            a=b=p[id].lower_bound(pos),a--,b++;
            modify(1,*b,root);
            modify(1,*a,root);
            if(p[id].size()==3) num--;
            p[id].erase(p[id].find(pos));
        }
        if(opt==1)
        {
            a=b=p[id].lower_bound(pos),a--;
            modify(1,root);
            if(p[id].size()==2) num++;
            p[id].insert(pos);
        }
        if(opt==2) ans[id]=query(pos);
    }
    for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
    return 0;
}