線段樹合併分裂學習筆記
阿新 • • 發佈:2020-10-18
線段樹合併分裂學習筆記
思想
你想想你寫一顆普通線段樹是怎麼寫的,是不是把子區間的資訊合併到父區間?
線段樹合併大概就是這個想法,在樹上每一個節點維護一顆權值線段樹,把兩棵線段樹的資訊合併到一個線段樹上
線段樹分裂呢,就是把一棵權值線段樹根據排名或值來割裂成兩棵權值線段樹,思路和fhq_treap的分裂類似,如下圖
當然,由於給每一個節點都開一顆靜態線段樹空間肯定不夠,且節點上線段樹可能都不一定滿,所以我們愉快地投進動態開點線段樹的懷抱中.
這兩操作的一次的時間複雜度都是\(O(log_{2}n)\)的
例題
1.P4556 [Vani有約會]雨天的尾巴 /【模板】線段樹合併
這是板子嗎?
對於新增\((x,y)\)路徑上的所有點,暴力肯定是不行的,這裡有一個經典解法是樹上差分,即在\(x\),\(y\)上\(+1\),\(lca(x,y)\)上\(-1\),\(fa[lca(x,y)]\)上\(-1\)(畫畫圖就懂了)
然後我們要處理的問題就是資訊如何從子節點傳上父節點,就線段樹合併完事了.
注意合併時若兩棵樹一棵為空,返回另一棵即可
#include<bits/stdc++.h> using namespace std; int const MAXN=1e5+10,MAXM=2e7; int n,m,tot,tott,_,N; int h[MAXN],vis[MAXN],siz[MAXN],hson[MAXN],top[MAXN],fa[MAXN],dep[MAXN],val[MAXM],lc[MAXM],rc[MAXM],root[MAXM],ans[MAXM],X[MAXN],Y[MAXN],Z[MAXN],ansn[MAXN]; struct edge{ int to,next; }e[MAXN<<1]; inline int read(){ int x=0,f=1;char c=getchar(); while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();} return f*x; } inline void add(int u,int v){ e[++tot].to=v,e[tot].next=h[u],h[u]=tot; } void dfs1(int x,int dad){ siz[x]=1;fa[x]=dad; int maxn=-1; for(int i=h[x],to;i;i=e[i].next){ to=e[i].to; if(to==dad)continue; dep[to]=dep[x]+1; dfs1(to,x); siz[x]+=siz[to]; if(siz[to]>maxn)maxn=siz[to],hson[x]=to; } return; } void dfs2(int x,int topf){ top[x]=topf; if(!hson[x])return; dfs2(hson[x],topf); for(int i=h[x],to;i;i=e[i].next){ to=e[i].to; if(to==fa[x] || to==hson[x])continue; dfs2(to,to); } return; } inline int lca(int x,int y){ while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]])swap(x,y); x=fa[top[x]]; } if(dep[x]<dep[y])return x; else return y; } void change(int &x,int l,int r,int p,int a){ if(!x)x=++tott; if(l==r){val[x]+=a;if(val[x]>0)ans[x]=p;return;} int mid=(l+r)>>1; if(p<=mid)change(lc[x],l,mid,p,a); if(p>mid)change(rc[x],mid+1,r,p,a); if(val[lc[x]]>=val[rc[x]]){ val[x]=val[lc[x]]; ans[x]=ans[lc[x]]; if(!val[x])ans[x]=0; }else{ val[x]=val[rc[x]]; ans[x]=ans[rc[x]]; if(!val[x])ans[x]=0; } } int merge(int x,int y,int l,int r){ if(!x)return y;if(!y)return x; if(l==r){val[x]+=val[y];if(val[x]>0)ans[x]=l;return x;} int mid=(l+r)>>1; lc[x]=merge(lc[x],lc[y],l,mid); rc[x]=merge(rc[x],rc[y],mid+1,r); if(val[lc[x]]>=val[rc[x]]){ val[x]=val[lc[x]]; ans[x]=ans[lc[x]]; if(!val[x])ans[x]=0; }else{ val[x]=val[rc[x]]; ans[x]=ans[rc[x]]; if(!val[x])ans[x]=0; } return x; } void DFS(int x){ //printf("DFS in %d :",x); for(int i=h[x];i;i=e[i].next){ int to=e[i].to; if(to==fa[x])continue; DFS(to); root[x]=merge(root[x],root[to],1,N); } ansn[x] = ans[root[x]]; return; } void test(int x,int l,int r){ printf("%d %d %d\n",l,r,val[x]); if(l==r)return ; int mid=(l+r)>>1; if(lc[x])test(lc[x],l,mid); if(rc[x])test(rc[x],mid+1,r); return; } int main(){ //freopen("data.in","r",stdin); //freopen("data.out","w",stdout); n=read();m=read(); for(int i=1,a,b;i<=n-1;i++){ a=read(),b=read(); add(a,b);add(b,a); } dep[1]=1; dfs1(1,1);dfs2(1,1); for(int i=1;i<=m;i++){ X[i]=read(),Y[i]=read(),Z[i]=read(); N=max(N,Z[i]); } for(int i=1;i<=m;i++){ int x=X[i],y=Y[i],z=Z[i]; change(root[x],1,N,z,1); change(root[y],1,N,z,1); int F=lca(x,y); change(root[F],1,N,z,-1); if(fa[F]!=F)change(root[fa[F]],1,N,z,-1); } DFS(1); for(int i=1;i<=n;i++){ printf("%d\n",ansn[i]); } return 0; }
Emm……當然可以二分答案+01序列排序來解決
但是,我們可是線段樹合併和分裂的學習筆記丫!
而且線段樹合併和分裂的速度甩了上一種不知道多少,而且還可以線上
先在每一個位置上建一棵權值線段樹,對於一段區間我們合併節點,並用標記來表示它是正序還是倒序,用set來維護現存所有區間的左端點.當要排序的區間跨過原本有序的區間時,按照位置分裂原區間再進行合併
複雜度\(O(nlog_{2}n)\)
#include<bits/stdc++.h> using namespace std; int const MAXN=1e5+10,MAXM=MAXN*70; int n,m,tot,tott,_,q; int val[MAXM],lc[MAXM],rc[MAXM]; int root[MAXN]; bool sta[MAXN]; set<int>S; typedef set<int>::iterator Sit; void insert(int &x,int l,int r,int p,int a){ if(!x)x=++tot; if(l==r){val[x]+=a;return;} int mid=(l+r)>>1; if(p<=mid)insert(lc[x],l,mid,p,a); else insert(rc[x],mid+1,r,p,a); val[x]=val[lc[x]]+val[rc[x]]; } void merge(int &x,int y){ if(!(x&&y)){x|=y;return;} val[x]+=val[y]; merge(lc[x],lc[y]); merge(rc[x],rc[y]); } void split(int x,int &y,int s,int op){ if(val[x]==s)return; val[y=++tot]=val[x]-s;val[x]=s; if(op==0){ if(val[lc[x]]>=s){split(lc[x],lc[y],s,op);rc[y]=rc[x];rc[x]=0;} else split(rc[x],rc[y],s-val[lc[x]],op); }else{ if(val[rc[x]]>=s){split(rc[x],rc[y],s,op);lc[y]=lc[x],lc[x]=0;} else split(lc[x],lc[y],s-val[rc[x]],op); } } int query(int x,int l,int r){ if(l==r)return l; int mid=(l+r)>>1; if(lc[x])return query(lc[x],l,mid); else return query(rc[x],mid+1,r); } Sit find(int p){ Sit k=S.lower_bound(p); if(*k==p)return k; --k;split(root[*k],root[p],p-*k,sta[p]=sta[*k]); return S.insert(p).first; } int main(){ scanf("%d%d",&n,&m); S.insert(n+1); for(int i=1;i<=n;i++){ int x;scanf("%d",&x); //insert(root[i],1,n,x,1); insert(root[i],1,n,x,1); S.insert(i); } for(int i=1;i<=m;i++){ int op,l,r;scanf("%d%d%d",&op,&l,&r); Sit nl=find(l),nr=find(r+1); for(Sit j=++nl;j!=nr;++j)merge(root[l],root[*j]); sta[l]=op;S.erase(nl,nr); } scanf("%d",&q); find(q);find(q+1); printf("%d\n",query(root[q],1,n)); return 0; }