洛谷 P3835 【模板】可持久化平衡樹
這個題也是可以用可持久化線段樹來解決的。
值域線段樹(也有的叫權值線段樹)可以用來維護一個可重集,並實現一些一般情況下平衡樹才能實現的事情。
如果用值來當做區間左右端點,每個葉子節點上存某個值出現的次數,非葉子節點上存一定範圍內的值出現的總次數,就可以建成值域線段樹。可以在上面直接查詢第k大值、小於某值的數的個數等等,具體請百度或參見代碼。
如何將線段樹可持久化呢?線段樹在單點更新的時候會經過log n個節點,每一次更新時顯然也只有這麽多節點會發生變化。
記錄每一個版本的線段樹的根節點,每一次操作前將根節點賦為與這次操作基於的版本的根節點相同。在更新操作時,備份每一個經過的節點(包括各個屬性:左、右子樹以及區間和),然後再進行修改。具體也可以參考可持久化線段樹的題解。
如果直接用可持久化的值域線段樹,顯然空間是不夠的(4*2e9個節點啊...)。現在有兩種選擇:
1.發現這道題沒有加、減操作,所有操作涉及的值都是確定的。因此可以進行離散化,然後再做,想必可以A掉吧(我沒試過)
2.可以寫動態開點線段樹。題目要求的集合一開始是空的,因此如果一開始建一棵完整的線段樹的話,每一個節點記錄的區間和都是0。而總共只有5e5次操作,每一次操作涉及更改節點最多有log2(2e9)=31個,兩者乘起來遠遠小於4*2e9。
可以考慮一開始不真正建樹。規定:如果某節點的某個子節點是一個特殊的標記的話,表明以這個子節點為根的子樹還沒有實際建出來。顯然,一個子樹沒有實際建出來的時候,其表示的區間的和為0。(以下代碼中我用的標記是0)
在進行修改操作的時候,可能需要建出來要走入的那個子節點。在進行查詢操作的時候,可以把未建出的子節點的區間和當做0。
附:寫完後我發現前驅和後繼竟然是最難寫的...
附:註意各種對不存在的節點的查詢/要忽略的操作
附:註意代碼中有一些操作用到的變量被設置成了全局變量,還define了一個mid,表示區間中點,可能比較奇怪...
#include<cstdio> #include<algorithm> #include<cstring> #include<bits/stdc++.h> #define mid ((l+r)>>1) #define inf 2147483647 using namespace std; int lc[20000000],rc[20000000],root[20000000],dat[20000000],ll=-1e9,rr=1e9; //lc[i]、rc[i]分別表示節點i的左子節點、右子節點編號,如果lc[x]=0則表示x的左子樹尚未建出來,rc[x]同理 //dat[i]表示節點i表示的值域區間中各數的出現次數之和 int n,L,x,mem=1;//因為0號被"未實際建出的節點"的特殊標記占用了,1號被版本0的根節點占用了 void addx(int l,int r,int& num)//更新操作,將線段樹維護的集合中數L的出現次數加上x(x為1或-1) { int t=num;num=++mem;lc[num]=lc[t];rc[num]=rc[t];dat[num]=dat[t];//備份當前節點,如果當前節點原來為空則也可以完成 if(l==r) { if(!(dat[num]==0&&x<0)) dat[num]+=x;//如果L出現次數為0且操作為刪除操作,則忽略操作 return; } if(L<=mid) addx(l,mid,lc[num]); else addx(mid+1,r,rc[num]); dat[num]=0; if(lc[num]) dat[num]+=dat[lc[num]]; if(rc[num]) dat[num]+=dat[rc[num]];//維護當前節點信息 } int query(int l,int r,int num)//查詢集合中小於x的數的個數 { if(l==r) return 0;//如果已經到葉子節點了,那麽當前節點等於x,顯然不小於x if(!num) return 0;//如果當前節點為空,那麽該節點表示的子樹中數都沒有,自然返回0 if(x<=mid) return query(l,mid,lc[num]);//根據x決定向左/右子樹走 else return (lc[num]?dat[lc[num]]:0)+query(mid+1,r,rc[num]); } int query_kth(int l,int r,int k,int num)//查詢第k小數 { assert(num!=0);//assert(x)表示如果x為false則停止程序,是用來調試的。如果查詢操作是正常進行的,那麽不可能走到未建出的點中 if(l==r) {return l;} //if(!num) return 0;//沒有用 int ls=lc[num]?dat[lc[num]]:0; if(ls>=k) return query_kth(l,mid,k,lc[num]);//根據左子樹中數出現總次數決定向左/右子樹走 else return query_kth(mid+1,r,k-ls,rc[num]); } int query_time(int l,int r,int num)//查詢數x出現的次數 { while(l!=r) { if(!num) return 0;//當前節點未建出,表明其子節點均未出現 if(L<=mid) r=mid,num=lc[num]; else l=mid+1,num=rc[num]; } return dat[num]; } int query_pre(int l,int r,int num)//查詢數x的前驅 { int t=query(l,r,num);//t是集合中比x小的數的個數 if(t==0) return -inf;//如果集合中比x小的數有0個,則x是集合中最小的數,不存在前驅 return query_kth(l,r,t,num);//否則查詢集合中第t小即可 } int query_nxt(int l,int r,int num) { int t1=query(l,r,num),t2=query_time(l,r,num);//t1是集合中比x小的數的個數,t2是集合中x出現的次數,加起來是集合中小於等於x的數的個數 x=inf;int t3=query(l,r,num); if(t1+t2==t3) return inf;//如果集合中小於等於x的數與集合中小於等於inf的數相等,則x是集合中最大的數,不存在後繼 return query_kth(l,r,t1+t2+1,num);//否則查詢集合中第t1+t2+1小即可 } int main() { int i,v,idx; scanf("%d",&n); root[0]=1; for(i=1;i<=n;i++) { scanf("%d%d%d",&v,&idx,&x);root[i]=root[v]; if(idx==1) { L=x;x=1; addx(ll,rr,root[i]); } else if(idx==2) { L=x;x=-1; addx(ll,rr,root[i]); } else if(idx==3) { printf("%d\n",query(ll,rr,root[i])+1); } else if(idx==4) { printf("%d\n",query_kth(ll,rr,x,root[i])); } else if(idx==5) { L=x; printf("%d\n",query_pre(ll,rr,root[i])); } else if(idx==6) { L=x; printf("%d\n",query_nxt(ll,rr,root[i])); } } return 0; }
洛谷 P3835 【模板】可持久化平衡樹