1. 程式人生 > >牛客練習賽34 F little w and Discretization(可持久化線段樹)

牛客練習賽34 F little w and Discretization(可持久化線段樹)

 

 

猛然發現,過這道題竟然已經是10天前的事情了……

題意大概就是說給你一個序列,每次詢問把一個區間的數字離散化,問離散化之後與原本數字不相同的數字個數有多少個。這裡有很多個詢問,每個詢問之間相互獨立。

既然是要離散化,我們肯定不能到詢問的時候再離散化,肯定是在做所有詢問之前,先自己離散化一下。我們考慮一個數字離散化之後與它本身的區別,由於這裡說了數列裡面的數字都是1~1e9的,所以一個數字離散化之後,肯定是小於等於它本身的。我們要求的是一個區間內,離散化之後與原本數字不相同的個數,我們可以考慮反過來可以求與原本數字相同的個數。對於一個數字,如果在整個區間所有數字離散化情況下為x,那麼只離散化一個小區間的時候,數字肯定是小於等於x的。所以說,對於整體離散化之後,如果某個數字已經小於它本身了,那麼這個數字就可以不再考慮。

略掉那些直接可以不考慮的數字之後,我們看看剩下的數字怎麼處理。觀察後可以發現,如果在一個區間內,某一個數字x已經比它離散化之後的數字大了,那麼所有大於x的數字一定都會比它對應離散化之後的數字大。那麼,我們的問題就轉化為了,求一個區間裡面最大的一個數字,使得其離散化之後的數字與其本身相同。再一分析,我們可以發現,相當於求一個區間的mex,也即從1開始最大的沒有出現過的整數。找到這個mex之後,我們還需要知道這個區間裡面有多少個小於等於mex的數字,用區間長度減去這個數字就是我們最後的答案。

那麼,現在問題就是如何求區間mex和區間小於等於mex的數字個數。我們考慮用可持久化線段樹,對於每一個子線段樹,我儲存前i個數字離散化後的屬性。每一個棵樹維護區間[1,n]表示到目前位置每個數字最後晚出現的時間,沒有出現則為0,然後維護區間最小值。對於詢問(l,r),我在第r棵線段樹裡面找到一個小於l的最小的位置,這個位置就是我們想要的mex。然後就是求小於等於mex的數字個數,這個也是一樣,在可持久化線段樹裡面多維護一個數字個數即可。

這題在比賽的時候已經想出,但是有一個小地方沒有注意到。我們在初始的時候,就可以略去一些一定不會與離散化後數字相等的數字,這些數字不需要加入可持久化線段樹,但是線段樹的繼承資訊不能因為這個不加入而斷開。這一點我疏忽了,所以GG……具體見程式碼:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 300100;
 
int n,m,num[N],a[N],b[N];
 
int rt[N<<4];                         //空間一定要開大,不要吝嗇
 
struct Persistent_SegTree  
{  
    struct node{int l,r,min,sum;} T[N<<5];  
    int cnt; void init(){cnt=0;} 
   
    void ins(int &i,int old,int l,int r,int x,int y)  
    {   
        T[i=++cnt]=T[old]; 
        int mid=(l+r)>>1; T[i].sum++; 
        if (l==r) {T[i].min=y;return;}  
        if (x<=mid) ins(T[i].l,T[old].l,l,mid,x,y);  
               else ins(T[i].r,T[old].r,mid+1,r,x,y);
        T[i].min=min(T[T[i].l].min,T[T[i].r].min);  
    }  
   
    int query(int i,int l,int r,int x)  
    {  
        if (l==r) return l;  
        int mid=(l+r)>>1;  
        if (T[T[i].l].min<x) return query(T[i].l,l,mid,x);  
                    else return query(T[i].r,mid+1,r,x);  
    }  
 
    int getsum(int i,int j,int l,int r,int L,int R)  
    {  
        //cout<<L<<' '<<R<<endl;
        if (l==L&&R==r) return T[j].sum-T[i].sum;  
        int mid=(L+R)>>1;  
        if (r<=mid) return getsum(T[i].l,T[j].l,l,r,L,mid);  
        else if (l>mid) return getsum(T[i].r,T[j].r,l,r,mid+1,R);  
        else return getsum(T[i].l,T[j].l,l,mid,L,mid)+getsum(T[i].r,T[j].r,mid+1,r,mid+1,R);  
    }  
   
} Persist;
 
 
int main()
{
    scanf("%d",&n);
    Persist.init();
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        num[i]=b[i]=a[i];
    }
    sort(num+1,num+1+n);
    int tot=unique(num+1,num+1+n)-num-1;
    for(int i=1;i<=n;i++)
    {
        a[i]=lower_bound(num+1,num+1+tot,a[i])-num;
        if (a[i]==b[i]) Persist.ins(rt[i],rt[i-1],1,tot,a[i],i);
                else rt[i]=rt[i-1];            //這個繼承一定不能斷開
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        int num=Persist.query(rt[r],1,tot,l);
        //cout<<num<<endl;
        int t=Persist.getsum(rt[l-1],rt[r],1,num,1,tot);
        printf("%d\n",r-l+1-t); 
    }
    return 0;
}