线段树合并学习笔记
线段树合并用于解决一些需要将两颗线段树合并的题目
常见于一些子树处理的题目。
动态开点,记录左右子树。
不得不说,merge的代码像极了FHQtreap
inline int merge(int a,int b,int x,int y){ if(!a)return b; if(!b)return a; if(x==y)/*do whatever you need and return a*/ int mid=(x+y)>>1; tr[a].l=merge(tr[a].l,tr[b].l,x,mid); tr[a].r=merge(tr[a].r,tr[b].r,mid+1,y); pushup(a); return a; }
然后是更新,其实代码很好写……
inline void update(int &now,int l,int r,int x){ if(!now)now=++cnt; if(l==r)/*do whatever you need*/ int mid=(l+r)>>1; if(mid>=x)update(tr[now].l,l,mid,x); else update(tr[now].r,mid+1,r,x); pushup(now); }
然后推荐几道例题:
1.CF600E
思路:对于每一个节点,它的线段树等于所有子节点线段树的合并在加上它自己。
代码如下:
#include<bits/stdc++.h> using namespace std; #define int long long const int maxn=2*1e5+10; inline int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;char c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();} return x*f; } struct node{ int l,r,val,sum,ans; }tr[maxn*25]; int n,c[maxn],root[maxn],cnt,ans[maxn]; int beg[maxn],nex[maxn<<1],to[maxn<<1],e; inline void add(int x,int y){ e++;nex[e]=beg[x]; beg[x]=e;to[e]=y; } inline void pushup(int now){ int l=tr[now].l,r=tr[now].r; if(tr[l].sum>tr[r].sum){ tr[now].ans=tr[l].ans; tr[now].sum=tr[l].sum; tr[now].val=tr[l].val; } if(tr[l].sum<tr[r].sum){ tr[now].ans=tr[r].ans; tr[now].sum=tr[r].sum; tr[now].val=tr[r].val; } if(tr[l].sum==tr[r].sum){ tr[now].ans=tr[l].ans+tr[r].ans; tr[now].sum=tr[l].sum; tr[now].val=tr[l].val; } } inline void update(int &now,int l,int r,int x){ if(!now)now=++cnt; if(l==r){ tr[now].val=l; tr[now].sum++; tr[now].ans=l; return; } int mid=(l+r)>>1; if(mid>=x)update(tr[now].l,l,mid,x); else update(tr[now].r,mid+1,r,x); pushup(now); } inline int merge(int a,int b,int l,int r){ if(!a)return b; if(!b)return a; if(l==r){ tr[a].ans=tr[a].val=l; tr[a].sum+=tr[b].sum; return a; } int mid=(l+r)>>1; tr[a].l=merge(tr[a].l,tr[b].l,l,mid); tr[a].r=merge(tr[a].r,tr[b].r,mid+1,r); pushup(a); return a; } inline void dfs(int fa,int x){ for(int i=beg[x];i;i=nex[i]){ int t=to[i]; if(t==fa)continue; dfs(x,t); merge(root[x],root[t],1,n); } update(root[x],1,n,c[x]); ans[x]=tr[root[x]].ans; } signed main(){ n=read(); for(int i=1;i<=n;i++){ c[i]=read(); root[i]=i; } int x,y; for(int i=1;i<n;i++){ x=read(),y=read(); add(x,y),add(y,x); } cnt=n; dfs(0,1); for(int i=1;i<=n;i++) printf("%lld ",ans[i]); puts(""); return 0; }
2.P3605
思路:1.首先离散化(常规套路);
2.建权值线段树;
3.合并子节点(也是常规套路);
代码如下:
//p3605,线段树合并,score=100 #include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; int n,p[maxn],tmp[maxn],ans[maxn],rt[maxn],cnt; int beg[maxn],nex[maxn<<1],to[maxn<<1],e; 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<<1)+(x<<3)+c-'0';c=getchar();} return x*f; } inline void add(int x,int y){ e++;nex[e]=beg[x]; beg[x]=e;to[e]=y; } struct node{ int l,r,val; }tr[maxn*50]; inline int query(int h,int l,int r,int x,int y){ if(l>y||r<x)return 0; if(l>=x&&r<=y)return tr[h].val; int mid=(l+r)>>1; return query(tr[h].l,l,mid,x,y)+query(tr[h].r,mid+1,r,x,y); } inline void update(int &now,int l,int r,int x){ if(!now)now=++cnt; if(l==r){ tr[now].val++; return; } int mid=(l+r)>>1; if(mid>=x)update(tr[now].l,l,mid,x); else update(tr[now].r,mid+1,r,x); tr[now].val=tr[tr[now].l].val+tr[tr[now].r].val; } inline int merge(int a,int b,int l,int r){ if(!a)return b; if(!b)return a; if(l==r){ tr[a].val+=tr[b].val; return a; } int mid=(l+r)>>1; tr[a].l=merge(tr[a].l,tr[b].l,l,mid); tr[a].r=merge(tr[a].r,tr[b].r,mid+1,r); tr[a].val=tr[tr[a].l].val+tr[tr[a].r].val; return a; } inline void dfs(int x,int fa){ for(int i=beg[x];i;i=nex[i]){ int t=to[i]; if(t==fa)continue; dfs(t,x); merge(rt[x],rt[t],1,n); } ans[x]=query(rt[x],1,n,p[x],n); update(rt[x],1,n,p[x]); } int main(){ n=read(); for(int i=1;i<=n;i++){ tmp[i]=p[i]=read(); rt[i]=i; } sort(tmp+1,tmp+1+n); for(int i=1;i<=n;i++) p[i]=lower_bound(tmp+1,tmp+1+n,p[i])-tmp; int x; for(int i=2;i<=n;i++){ x=read(); add(i,x),add(x,i); } cnt=n; dfs(1,0); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
3.雨天的尾巴
有了线段树合并的基础后,很容易想到差分最后线段树合并。
这道题跟hdu上的relief grain是重题,然而我们老师却把那道题当做树剖的练手题给我们。
害得我想了好久……
代码如下:
//p3605,线段树合并,score=100 #include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; int n,p[maxn],tmp[maxn],ans[maxn],rt[maxn],cnt; int beg[maxn],nex[maxn<<1],to[maxn<<1],e; 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<<1)+(x<<3)+c-'0';c=getchar();} return x*f; } inline void add(int x,int y){ e++;nex[e]=beg[x]; beg[x]=e;to[e]=y; } struct node{ int l,r,val; }tr[maxn*50]; inline int query(int h,int l,int r,int x,int y){ if(l>y||r<x)return 0; if(l>=x&&r<=y)return tr[h].val; int mid=(l+r)>>1; return query(tr[h].l,l,mid,x,y)+query(tr[h].r,mid+1,r,x,y); } inline void update(int &now,int l,int r,int x){ if(!now)now=++cnt; if(l==r){ tr[now].val++; return; } int mid=(l+r)>>1; if(mid>=x)update(tr[now].l,l,mid,x); else update(tr[now].r,mid+1,r,x); tr[now].val=tr[tr[now].l].val+tr[tr[now].r].val; } inline int merge(int a,int b,int l,int r){ if(!a)return b; if(!b)return a; if(l==r){ tr[a].val+=tr[b].val; return a; } int mid=(l+r)>>1; tr[a].l=merge(tr[a].l,tr[b].l,l,mid); tr[a].r=merge(tr[a].r,tr[b].r,mid+1,r); tr[a].val=tr[tr[a].l].val+tr[tr[a].r].val; return a; } inline void dfs(int x,int fa){ for(int i=beg[x];i;i=nex[i]){ int t=to[i]; if(t==fa)continue; dfs(t,x); merge(rt[x],rt[t],1,n); } ans[x]=query(rt[x],1,n,p[x],n); update(rt[x],1,n,p[x]); } int main(){ n=read(); for(int i=1;i<=n;i++){ tmp[i]=p[i]=read(); rt[i]=i; } sort(tmp+1,tmp+1+n); for(int i=1;i<=n;i++) p[i]=lower_bound(tmp+1,tmp+1+n,p[i])-tmp; int x; for(int i=2;i<=n;i++){ x=read(); add(i,x),add(x,i); } cnt=n; dfs(1,0); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
深深地感到自己的弱小。