動態主席樹

ZOJ 2112
題目大意:給n個數,有m個操作。修改某個數,或者詢問一段區間的第k小值。
動態主席樹的意思就是原來的數組已經建好主席樹了,然後又要修改數組中的某個值,然後還有許多查詢,當然不止一次修改。
如果我們每次都建立主席樹的話假如有n個數m次修改就是n*m的時間複雜度,這在n,m很大的時候就不行了,於是我們就新引進樹狀數組來存數組的變化值,當每次查詢的時候只要將原來主席樹裏的值算出來,加上變化量就是當前的值了。
具體的樹狀數組怎麼實現呢?
普通的修改一個值的樹狀數組相信很多人都會吧,不會的自行去百度在這裏不做過多的解釋。主席樹就相當是一個前綴,所以可以相減解決區間問題,只不過這裏的每個點代表的是一顆線段樹。所以我們可以在這裏可以用樹狀數組更新,存儲變化值,每個點的修改都是一棵線段樹,比如將第2個數進行修改,本來是2現在改成5,樹狀數組本來就會在相應的點+3,而現在是線段樹,就可以現在代表2的位置-1再在代表5的位置+1這樣就完成了修改,這是每個單點的修改,其他的就用樹狀數組進行對應的修改就好了,查詢的時候也是用樹狀數組的查詢方式加上原來主席樹裏的值就好了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=10000*15*16;
/*
數組註解:
root:代表主席樹的每個數字對應點的節點編號
a:原數組的數
hash:離散化是二分的數組
root2:樹狀數組裏線段樹的根節點編號
rootl:每次查找的多個樹狀數組左區間節點編號
rootr:每次查找的多個樹狀數組右區間節點編號
*/
int a[50010],Hash[60010],root[50010],sz,n,m;
int root2[50010],rootl[50],rootr[50];
int cntl,cntr;//每次更新時左右區間樹狀數組需要查找的最多節點數
struct Query//存儲操作
{
    int opt,i,j,k;
} q[10010];
int get_hash(int x)//hash值
{
    return lower_bound(Hash+1,Hash+sz,x)-Hash;
}
struct chairman_of_tree
{
    struct zp
    {
        int l,r,sum;
        void clear()
        {
            l=r=sum=0;
        }
    } tree[maxn];
    int cont;
    void init()
    {
        sz=n+1;
        memset(root2,0,sizeof(root2));
        cont=1;
        tree[0].clear();
    }
    void update(int pre,int &k,int l,int r,int num,int val)//建立靜態主席樹
    {
        tree[cont]=tree[pre],tree[cont].sum+=val,k=cont++;
        if(l==r) return ;
        int mid=(l+r)>>1;
        if(l<r)
        {
            if(num<=mid)
                update(tree[pre].l,tree[k].l,l,mid,num,val);
            else
                update(tree[pre].r,tree[k].r,mid+1,r,num,val);
        }
    }
    int lowbit(int x)
    {
        return x&(-x);
    }
    void arr_update(int &rt,int l,int r,int pos,int val)//將第k個數改爲val,更新樹狀數組
    {
        if(rt==0)
        {
            rt=cont++;
            tree[rt].clear();
        }
        tree[rt].sum+=val;
        if(l==r) return ;
        int mid=(l+r)>>1;
        if(pos<=mid) arr_update(tree[rt].l,l,mid,pos,val);
        else arr_update(tree[rt].r,mid+1,r,pos,val);
    }
    void arr_update(int k,int val)//將第k個數改爲val預處理
    {
        int x=get_hash(a[k]),y=get_hash(val);
        a[k]=val;
        while(k<=n)
        {
            arr_update(root2[k],1,sz,x,-1);//從樹狀數組裏先減去原值
            arr_update(root2[k],1,sz,y,1);//給新值加上1
            k+=lowbit(k);//樹狀數組原理更新每一個線段樹
        }
    }
    int query(int ql,int qr,int l,int r,int k)//查詢l~r的區間第k個數
    {
        if(l==r) return l;
        int sum=tree[tree[qr].l].sum-tree[tree[ql].l].sum,mid=(l+r)>>1;//先求出主席樹的兩個節點的差值
        for(int i=0; i<cntr; i++) sum+=tree[tree[rootr[i]].l].sum;//加上樹狀數組裏右區間的前綴和,代表右區間以前的變化量
        for(int i=0; i<cntl; i++) sum-=tree[tree[rootl[i]].l].sum;//同上代表左區間的變化量,注意是減去,這纔是本區間的變化量
        //sum代表的就是現在區間內的值是多少
        if(sum>=k)
        {
            for(int i=0; i<cntr; i++) rootr[i]=tree[rootr[i]].l;//樹狀數組所有的樹節點統一向左走
            for(int i=0; i<cntl; i++) rootl[i]=tree[rootl[i]].l;
            return query(tree[ql].l,tree[qr].l,l,mid,k);
        }
        else
        {
            for(int i=0; i<cntr; i++) rootr[i]=tree[rootr[i]].r;//同上向右走
            for(int i=0; i<cntl; i++) rootl[i]=tree[rootl[i]].r;
            return query(tree[ql].r,tree[qr].r,mid+1,r,k-sum);
        }
    }
    int kth(int l,int r,int k)//查詢l~r的區間第k個數預處理
    {
        cntl=cntr=0;//把樹狀數組裏面會影響到本次查詢的修改都存在數組裏面
        for(int i=l-1; i; i-=lowbit(i)) rootl[cntl++]=root2[i];//左區間
        for(int i=r; i; i-=lowbit(i)) rootr[cntr++]=root2[i];//右區間
        return Hash[query(root[l-1],root[r],1,sz,k)];
    }
} ac;
int main()
{
    int ncase;
    scanf("%d",&ncase);
    while(ncase--)
    {
        scanf("%d%d",&n,&m);
        ac.init();
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]),Hash[i]=a[i];
        char c;
        for(int i=0; i<m; i++)
        {
            scanf(" %c",&c);
            if(c=='Q')
            {
                q[i].opt=0;
                scanf("%d%d%d",&q[i].i,&q[i].j,&q[i].k);
            }
            else
            {
                q[i].opt=1;
                scanf("%d%d",&q[i].i,&q[i].j);
                Hash[sz++]=q[i].j;
            }
        }
        sort(Hash+1,Hash+sz);//離散化
        for(int i=1; i<=n; i++) //建立主席樹
            ac.update(root[i-1],root[i],1,sz,get_hash(a[i]),1);
        for(int i=0; i<m; i++)
        {
            if(q[i].opt==0)
                printf("%d\n",ac.kth(q[i].i,q[i].j,q[i].k));
            else
                ac.arr_update(q[i].i,q[i].j);
        }
    }
}
發佈了110 篇原創文章 · 獲贊 18 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章