主席树,喵~
稍微總結(jié)一下主席樹吧
Too Difficult!搞了一天搞出一大堆怎么令人悲傷的辣雞代碼。總之先總結(jié)一下吧,以后碰到這種問(wèn)題直接拿去毒害隊(duì)友好了。
UPD 5/24 茍狗是沙比
一個(gè)節(jié)點(diǎn)記錄三個(gè)信息:lson,rson,sum
用pid表示節(jié)點(diǎn)個(gè)數(shù)。
build
void build(int &k,int l,int r){k=++pid;if(l==r) return;int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r); }change
void change(int old,int &k,int l,int r,int pos,int x){k=++pid;lson[k]=lson[old],rson[k]=rson[old],sum[k]=sum[old]+x;if(l==r) return;int mid=(l+r)>>1;if(pos<=mid) change(lson[old],lson[k],l,mid,pos,x);else change(rson[old],rson[k],mid+1,r,pos,x); }Lv.1 最基本的操作
- 區(qū)間k大值
- 區(qū)間內(nèi)有多少個(gè)數(shù)字小于等于x
- 查詢區(qū)間<=x的最大數(shù)字:上兩條的組合技。
兩道入門題:POJ2104,HDU4417
主席樹相當(dāng)于對(duì)每一個(gè)前綴都維護(hù)一個(gè)線段樹,然后發(fā)現(xiàn)相鄰兩棵線段樹長(zhǎng)得好像哎!所以我們可以動(dòng)態(tài)開點(diǎn)啦!
解決問(wèn)題的時(shí)候,我們通常會(huì)對(duì)每一個(gè)前綴,維護(hù)一個(gè)權(quán)值線段樹。每個(gè)值域存要維護(hù)的信息。
既然是維護(hù)每一個(gè)前綴,所以,我們不僅能拿主席樹來(lái)施展線性結(jié)構(gòu),還能施展樹狀結(jié)構(gòu)!比如說(shuō)我們可以查詢樹上兩點(diǎn)間路徑點(diǎn)權(quán)的k小值。
Lv.2 樹上路徑上點(diǎn)權(quán)k小值
栗子:SPOJ-COT
線性結(jié)構(gòu)上
iterval(l,r)=T(r)-T(l-1)
樹狀結(jié)構(gòu)上
path(u,v) = T(u)+T(v)-T(lca)-T(Parent of lca)
Lv.2 矩形內(nèi)有多少個(gè)點(diǎn)
給出很多個(gè)點(diǎn)。Q組詢問(wèn),每組詢問(wèn)查詢一個(gè)矩形內(nèi)有幾個(gè)點(diǎn)。
按橫坐標(biāo)排序,把縱坐標(biāo)放到主席樹上,然后就相當(dāng)于區(qū)間內(nèi)有多少個(gè)數(shù)字小于等于x啦!
栗子:CF853C
把細(xì)節(jié)考慮好!還是很友好的。
#include <iostream> #include <algorithm> using namespace std; const int N=6000000+10; #define f(x) (1LL*x*(x-1)/2) typedef long long LL; int lson[N],rson[N],sum[N],root[N],pid; int n,q,p[N]; void build(int &k,int l,int r){k=++pid;if(l==r) return;int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r); } void change(int old,int &k,int l,int r,int pos,int x) {k=++pid;ilson[k]=lson[old],rson[k]=rson[old],sum[k]=sum[old]+x;if(l==r) return;int mid=(l+r)>>1;if(pos<=mid) change(lson[k],lson[k],l,mid,pos,x);else change(rson[k],rson[k],mid+1,r,pos,x); } int query(int new_k,int old_k,int l,int r,int x) { // cnt <= xif(x<l) return 0;if(l==r) return sum[new_k]-sum[old_k];int mid=(l+r)>>1;if(mid<x) return sum[lson[new_k]]-sum[lson[old_k]]+query(rson[new_k],rson[old_k],mid+1,r,x);else return query(lson[new_k],lson[old_k],l,mid,x); } int count(int x1,int x2,int y1,int y2) { // if(x1>x2||y1>y2) return 0;int cnt1 = query(root[x2],root[x1-1],1,n,y1-1);int cnt2 = query(root[x2],root[x1-1],1,n,y2);return cnt2-cnt1; } int main(){scanf("%d%d",&n,&q);for(int i=1;i<=n;i++) {scanf("%d",&p[i]);}build(root[0],1,n);for(int i=1;i<=n;i++) {change(root[i-1],root[i],1,n,p[i],1);}for(int i=1;i<=q;i++){int l,d,r,u;scanf("%d%d%d%d",&l,&d,&r,&u);int LU = count(1,l-1,u+1,n);int LD = count(1,l-1,1,d-1);int RU = count(r+1,n,u+1,n);int RD = count(r+1,n,1,d-1);int L = l-1; int U = n-u; int R = n-r; int D = d-1;LL A = f(L)+f(R)+f(U)+f(D);LL B = f(LU)+f(LD)+f(RU)+f(RD);LL ret = 1LL*n*(n-1)/2-(A-B);printf("%lld\n", ret);} }Lv.2 區(qū)間內(nèi)出現(xiàn)數(shù)字的個(gè)數(shù)
權(quán)值線段樹直接投降了,不過(guò)我們可以在某個(gè)元素上一次出現(xiàn)的位置insert -1,在當(dāng)前出現(xiàn)的位置insert 1
種樹之前想清楚該維護(hù)什么啊!
栗子: HDU5919
題解:因?yàn)槭墙y(tǒng)計(jì)區(qū)間內(nèi),每個(gè)數(shù)字第一次出現(xiàn)的位置。
所以我們可以倒著做。從后往前遍歷,遇到一個(gè)數(shù)字,在這個(gè)數(shù)字上一次出現(xiàn)的位置加上-1,當(dāng)前位置加上1.
在從后往前遍歷的同時(shí),我們對(duì)于每一個(gè)后綴建一棵線段樹。維護(hù)后綴中,每個(gè)元素第一次出現(xiàn)的位置。
對(duì)于每組詢問(wèn),先求出區(qū)間內(nèi)有多少種不同的數(shù)字,然后查詢第(cnt+1)/2大即可。
#include <iostream> #include <map> using namespace std; const int N = 10000000+10; int lson[N],rson[N],root[N],sum[N],pid; int T,cas;void build(int &k,int l,int r) {k=++pid;if(l==r) return;int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r); } void update(int old,int &k,int l,int r,int pos,int x) {k=++pid; sum[k] = 0;lson[k]=lson[old], rson[k]=rson[old], sum[k]=sum[old]+x;if(l==r) return;int mid=(l+r)>>1;if (pos<=mid) update(lson[old],lson[k],l,mid,pos,x);elseupdate(rson[old],rson[k],mid+1,r,pos,x); } int query_x_th(int k,int l,int r,int x) {if (l == r) return l;int mid = (l+r)>>1;if (sum[lson[k]] < x) {return query_x_th(rson[k],mid+1,r,x-sum[lson[k]]);} else {return query_x_th(lson[k],l,mid,x);} } int count(int k,int l,int r,int L,int R) {if(L<=l&&r<=R) {return sum[k];}int mid = (l+r)>>1;int ans = 0;if (L<=mid) ans += count(lson[k],l,mid,L,R);if (R >mid) ans += count(rson[k],mid+1,r,L,R);return ans; }int n, m, a[N]; map<int,int> las; void init() {las.clear();pid = 0; } int main() {scanf("%d",&T);while (T --) {init();scanf("%d %d",&n,&m);for(int i=1;i<=n;i++) {scanf("%d", &a[i]); }build(root[n+1],1,n);for(int i=n;i>=1;i--) {update(root[i+1],root[i],1,n,i,1);if ( las.find(a[i]) != las.end() )update(root[i],root[i],1,n,las[a[i]], -1);las[a[i]] = i;}printf("Case #%d:", ++cas);int ans=0;for(int i=1;i<=m;i++) {int l, r;scanf("%d %d", &l, &r);int nl = min((l+ans)%n+1, (r+ans)%n+1);int nr = max((l+ans)%n+1, (r+ans)%n+1);int tot = count(root[nl],1,n,nl,nr);ans = query_x_th(root[nl],1,n,(tot+1)/2);printf(" %d", ans);}printf("\n");} }Lv.3 主席樹的區(qū)間更新
一種不用下傳懶惰標(biāo)記的姿勢(shì):對(duì)于區(qū)間查詢,從上往下走的時(shí)候,對(duì)懶惰標(biāo)記進(jìn)行累加。
栗子:HDU4348
#include <iostream> #include <algorithm> #include <vector> using namespace std; typedef long long LL; const int N=6000000+10; int lson[N],rson[N],root[N],pid; LL sum[N],lazy[N]; int n,q,a[N];void build(int &k,int l,int r){k=++pid; lazy[k] = 0; sum[k] = 0;if(l==r) {sum[k] = a[l];lson[k] = rson[k] = 0;return;}int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r);sum[k] = sum[lson[k]] + sum[rson[k]]; } void update(int old,int &k,int l,int r,int L,int R,int x){k=++pid; lazy[k] = 0; sum[k] = 0;lazy[k]=lazy[old]; sum[k] = sum[old];lson[k]=lson[old]; rson[k]=rson[old];if(L<=l&&r<=R) {lazy[k] = lazy[old] + x;sum[k] = sum[old] + 1LL*(r-l+1)*x;return;}int mid=(l+r)>>1;if (L<=mid)update(lson[k],lson[k],l,mid,L,R,x);if (R >mid)update(rson[k],rson[k],mid+1,r,L,R,x);sum[k] = sum[lson[k]] + sum[rson[k]] + 1LL*lazy[k]*(r-l+1); } LL query(int k,int l,int r,int add,int L,int R) {if (L<=l&&r<=R)return sum[k] + 1LL*(r-l+1)*add;add += lazy[k];int mid=(l+r)>>1;LL ans=0;if (L<=mid) ans += query(lson[k],l,mid,add,L,R);if (R >mid) ans += query(rson[k],mid+1,r,add,L,R);return ans; } int stamp = 0; void init() {stamp=0;pid=0; } int main(){while (~ scanf("%d%d",&n,&q)) {init();for(int i=1;i<=n;i++) scanf("%d",&a[i]);build(root[0],1,n);int id = 0;for(int i=1;i<=q;i++){char op[2]; int l,r,t;scanf("%s",op);if(op[0] == 'C') {scanf("%d%d%d",&l,&r,&t);update(root[stamp],root[stamp+1],1,n,l,r,t);stamp ++;}if(op[0] == 'Q') {scanf("%d%d",&l,&r);LL ans = query(root[stamp],1,n,0,l,r); printf("%lld\n", ans);}if(op[0] == 'H') { scanf("%d%d%d",&l,&r,&t);LL ans = query(root[t],1,n,0,l,r);printf("%lld\n", ans);}if(op[0] == 'B'){scanf("%d",&t);stamp = t;}}} }一些練習(xí)
CF650D
題意:動(dòng)態(tài)LIS,每次修改一個(gè)位置,每次操作查詢LIS,操作相互獨(dú)立
題解:
兩種情況
第一種,更新后pos,出現(xiàn)在了LIS中
我們要做的是:查詢[1,pos)中,h<h[pos]的所有數(shù)字,LIS的max
可以對(duì)每一個(gè)前綴維護(hù)一個(gè)h的權(quán)值線段樹,每個(gè)節(jié)點(diǎn)記錄h在此值域內(nèi)LIS的max
第二種,更新后pos,沒(méi)出現(xiàn)在LIS中
判斷一下pos是否在存在于所有的,原序列LIS中。
這個(gè)地方很有趣。
hint: dp[i]+rev_dp[i]=LIS+1
Bonus: 1. 存在一個(gè)LIS包含元素i的條件 2. 所有LIS包含元素i的條件 #include <iostream> #include <cmath> #include <cstring> #include <algorithm> #include <vector> using namespace std;const int N = 400000+10; const int INF = 1000000007;int bit[N]; vector<int> v; int id(int x) {return lower_bound(v.begin(),v.end(),x)-v.begin()+1; } int get(int x) {int ans=0;while(x) {ans=max(ans,bit[x]);x-=x&-x;}return ans; } void upd(int pos,int x){while(pos<N) {bit[pos]=max(bit[pos],x);pos += pos&-pos;} } int n,m,h[N],dp[N],rdp[N],neccesary[N]; int LIS=0; vector<int> pos[N]; void compress(int on) {v.clear();if (on == 0) {for(int i=1;i<=n;i++) v.push_back(h[i]);} else {for(int i=1;i<=n;i++) v.push_back(INF-h[i]);}sort(v.begin(), v.end());v.erase(unique(v.begin(),v.end()),v.end()); } void LIS_Proccess() {scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) {scanf("%d",&h[i]);}compress(0);for(int i=1;i<=n;i++) {dp[i] = get(id(h[i])-1) + 1;upd(id(h[i]), dp[i]);LIS = max(LIS, dp[i]);}memset(bit,0,sizeof(bit));compress(1);for(int i=n;i>=1;i--) {rdp[i] = get(id(INF-h[i])-1) + 1;upd(id(INF-h[i]), rdp[i]);}for(int i=1;i<=n;i++) {if (dp[i]+rdp[i] == LIS+1) {pos[dp[i]].push_back(i);}}for(int i=1;i<=n;i++) {if (pos[i].size() == 1) {neccesary[pos[i][0]] = 1;}}}int lson[N*22],rson[N*22],val[N*22],root[N*22],pid; int ans[N], pre[N], suf[N], p[N], x[N]; void build(int &k,int l,int r) {k=++pid; val[k]=0;if(l==r) return;int mid=(l+r)>>1;build(lson[k],l,mid);build(rson[k],mid+1,r); } void change(int old,int &k,int l,int r,int pos,int x) {k=++pid;lson[k]=lson[old],rson[k]=rson[old],val[k]=max(x,val[old]);if(l==r) return;int mid=(l+r)>>1;if(pos<=mid) change(lson[old],lson[k],l,mid,pos,x);else change(rson[old],rson[k],mid+1,r,pos,x); } int query(int k,int l,int r,int L,int R) {if(L>R) return 0;if(L<=l&&r<=R) {return val[k];}int mid=(l+r)>>1;int ans=0;if (L<=mid) ans=max(ans, query(lson[k],l,mid,L,R));if (R >mid) ans=max(ans, query(rson[k],mid+1,r,L,R));return ans; }int main() {LIS_Proccess();// neccesary[i]: 第i位一定出現(xiàn)在LIS中pid=0; compress(0);build(root[0],1,v.size());for(int i=1;i<=n;i++) {change(root[i-1],root[i],1,v.size(),id(h[i]),dp[i]);}for(int i=1;i<=m;i++) {scanf("%d%d",&p[i],&x[i]);ans[i] = neccesary[p[i]] ? LIS - 1 : LIS;pre[i] = query(root[p[i]-1], 1, v.size(), 1, id(x[i])-1);}//exit(0);pid=0; compress(1);build(root[n+1],1,v.size());for(int i=n;i>=1;i--) {change(root[i+1],root[i],1,v.size(),id(INF-h[i]),rdp[i]);}for(int i=1;i<=m;i++) {suf[i] = query(root[p[i]+1], 1, v.size(), 1, id(INF-x[i])-1);ans[i] = max(ans[i], pre[i]+suf[i]+1);printf("%d\n", ans[i]);}}以上,于4/28,mark一下。
之后,待補(bǔ)的坑:
- BIT套主席樹 【學(xué)不會(huì)】
- 主席樹的區(qū)間更新【已補(bǔ)】
學(xué)數(shù)據(jù)結(jié)構(gòu)是不可能學(xué)數(shù)據(jù)結(jié)構(gòu)的,這輩子都不可能學(xué)數(shù)據(jù)結(jié)構(gòu)!
轉(zhuǎn)載于:https://www.cnblogs.com/RUSH-D-CAT/p/8965601.html
總結(jié)
- 上一篇: Ubuntu Linux 提出新的发布模
- 下一篇: ionic更改端口号