线段树合并小结
一种新的线段树 方法:
friend node operator + (const node &xx,const node &yy) { node z; z.cnt=xx.cnt+yy.cnt; z.sum=xx.sum+yy.sum; return z; }
然后改的时候就直接使用
tr[now]=tr[lid]+tr[rid];
权值线段树
就是把线段树变成桶。
用线段树维护桶。
代码:
#include<bits/stdc++.h> using namespace std; int n,k; struct segmentTree{ struct node{ int sum; }tr[40000<<2]; #define lid now<<1 #define rid now<<1|1 void update(int now,int l,int r,int x,int y,int k) { if(x<=l&&r<=y) { tr[now].sum=k;return ; } int mid=(l+r)>>1; if(x<=mid) update(lid,l,mid,x,y,k); if(y>mid) update(rid,mid+1,r,x,y,k); tr[now].sum=tr[lid].sum+tr[rid].sum; } int query(int now,int l,int r,int k) { if(l==r) return l; int mid=(l+r)>>1; if(tr[lid].sum>=k) query(lid,l,mid,k); else query(rid,mid+1,r,k-tr[lid].sum); } }st; int cnt; int main() { cin>>n>>k; for(int i=1;i<=n;i++) { int t;cin>>t; st.update(1,1,30000,t,t,1); // cout<<"upd\n"; } if(k<=0||k>=st.tr[1].sum) { cout<<"NO RESULT";return 0; } cout<<st.query(1,1,30000,k); }
作用
- 查询第 大的数。
int kth(int now,int l,int r,int k) { if(l==r) return l; int mid=(l+r)>>1; if(tr[lid].sum>=k) return kth(lid,l,mid,k); else return kth(rid,mid+1,r,k-tr[lid].sum); }
- 算逆序对(就是模板粘上去即可)
动态开点树
可以是线段树,也可以是权值线段树。
代码:
模板: P3369 【模板】普通平衡树
-
要注意的一点是, 的时候的 要写成
int &now
来更改值; -
每个操作 或者 的范围可以是负数、传进去的参数也可以是负数。
#include<bits/stdc++.h> #define int long long using namespace std; int n; int root=1,cnt=1; int mx=2e7+1; struct sgt{ struct node{ int sum,ls,rs; }tr[2000000<<1]; #define lid tr[now].ls #define rid tr[now].rs void update(int &now,int l,int r,int x,int y,int k) { if(!now) now=++cnt; if(x<=l&&r<=y) { tr[now].sum+=k; return ; } int mid=(l+r)>>1; if(x<=mid) update(lid,l,mid,x,y,k); if(y>mid) update(rid,mid+1,r,x,y,k); tr[now].sum=tr[lid].sum+tr[rid].sum; } int query(int now,int l,int r,int x,int y) { if(x<=l&&r<=y) return tr[now].sum; int mid=(l+r)>>1,res=0; if(x<=mid) res+=query(lid,l,mid,x,y); if(y>mid) res+=query(rid,mid+1,r,x,y); return res; } int kth(int now,int l,int r,int k) { if(l==r) return l; int mid=(l+r)>>1; if(tr[lid].sum>=k) return kth(lid,l,mid,k); else return kth(rid,mid+1,r,k-tr[lid].sum); } }st; map<int,int>mp; signed main() { cin>>n; for(int i=1;i<=n;i++) { int op,t; scanf("%lld%lld",&op,&t); if(op==1) { st.update(root,-mx,mx,t,t,1); mp[t]++; } else if(op==2) { st.update(root,-mx,mx,t,t,-1); mp[t]--; if(!mp[t]) mp.erase(t); } else if(op==3) { cout<<st.query(root,-mx,mx,-mx,t-1)+1<<endl; } else if(op==4) { int res=st.kth(root,-mx,mx,t); cout<<res<<endl; } else if(op==5) { cout<<(--mp.lower_bound(t))->first<<endl; } else if(op==6) { cout<<mp.upper_bound(t)->first<<endl; } } }
作用
缩小使用的空间。
例题
有的题目多解,可以直接使用权值线段树也可以使用线段树合并。
例题1 P3369 【模板】普通平衡树
看似是平衡树模板,实则是动态开点权值线段树模板。
只不过查找前驱后继的操作我拿 水过了。。
代码在上面。
例题2 #P1644. bzoj4636: 韶身的数列
这是拿动态开点权值线段树做的。
但是里面有一个操作很迷,就是修改比 小的数为 。
void ckmx(int &x,int y) { x=(y>x?y:x); } void update(int &now,int l,int r,int x,int y,int k) { if(!now) now=++cnt; if(x<=l&&r<=y) { ckmx(tr[now].mx,k);//这里 return ; } int mid=(l+r)>>1; if(x<=mid) update(lid,l,mid,x,y,k); if(y>mid) update(rid,mid+1,r,x,y,k); }
还有就是查询的神秘操作:
void query(int now,int l,int r,int pre) { if(!now&&!pre) return ; int mid=(l+r)>>1; int nnow=max(pre,tr[now].mx);//这里 if(l==r) ans+=nnow; else query(lid,l,mid,nnow),query(rid,mid+1,r,nnow); }
例题3 P3605 [USACO17JAN] Promotion Counting P
这个题很好,我拿动态开点权值线段树做的。
题意就是求树上逆序对,整体方法是 ,
对于一个答案 :
加上子树中比它大的个数 - 原本线段树中比它大的。
这样我们每 到一个节点 ,先在 中减去比 大的个数,然后 子节点,之后再给 加上线段树中比它大的。
最后,把当前节点的权值 进去。
void dfs(int u,int fa) { ans[u]-=st.query(root,1,mx,ww[u]+1,mx); for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==fa) continue; dfs(v,u); } ans[u]+=st.query(root,1,mx,ww[u]+1,mx); st.update(root,1,mx,ww[u],ww[u],1); }
然后有个细节:我们有可能 的节点是空的,所以在 函数中需要加上一行 if(!now) return 0;
具体来说是这样的:
int query(int now,int l,int r,int x,int y) { if(!now) return 0; if(x<=l&&r<=y) return tr[now].sum; int mid=(l+r)>>1,res=0; if(x<=mid) res+=query(lid,l,mid,x,y); if(y>mid) res+=query(rid,mid+1,r,x,y); return res; }
例题4 P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并
线段树合并 + 树链剖分
每个节点都是一个(动态开点)权值线段树,
然后树上差分:
- 到根节点加上 ;
- 到根节点加上 ;
- 到根节点减去 ;
- 到根节点减去 。
然后再一遍 dfs
合并线段树,至此结束全部的神秘操作。
线段树结构体数组要开 30 倍。
待完全理解。。
代码:
#include<bits/stdc++.h> using namespace std; int n,m; const int N=3e5+1; struct node{ int to,next; }edge[N<<1]; int head[N],ccnt; void add(int u,int v) { edge[++ccnt].next=head[u]; edge[ccnt].to=v; head[u]=ccnt; } int ans[N]; int siz[N],son[N],dep[N],f[N]; void dfs1(int u,int fa) { siz[u]=1,dep[u]=dep[fa]+1; f[u]=fa; for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==fa) continue; dfs1(v,u); siz[u]+=siz[v]; if(siz[son[u]]<siz[v]) son[u]=v; } } int top[N],id[N],tim; void dfs2(int u,int t) { id[u]=++tim;top[u]=t; if(!son[u]) return; dfs2(son[u],t); for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==f[u]||v==son[u]) continue; dfs2(v,v); } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]<dep[top[v]]) swap(u,v); u=f[top[u]]; } if(dep[u]>dep[v]) swap(u,v); return u; } int root=1,cnt=1; struct SegTree{ struct nodee{ int ls,rs,sum,tre; }tr[N*30]; #define lid tr[now].ls #define rid tr[now].rs void pushup(int now) { if(tr[lid].sum>=tr[rid].sum) tr[now].sum=tr[lid].sum,tr[now].tre=tr[lid].tre; else tr[now].sum=tr[rid].sum,tr[now].tre=tr[rid].tre; } void change(int &now,int l,int r,int pos,int val) { if(!now) now=++cnt; if(l==r) { tr[now].sum+=val; tr[now].tre=pos;return; } int mid=(l+r)>>1; if(pos<mid) change(lid,l,mid,pos,val); else change(rid,mid+1,r,pos,val); pushup(now); } int merge(int a,int b,int l,int r) { if(a==0||b==0) return a+b; if(l==r) { tr[a].sum+=tr[b].sum;return a; } int mid=(l+r)>>1; tr[a].ls=merge(tr[a].ls,tr[b].ls,l,mid); tr[a].rs=merge(tr[a].rs,tr[b].rs,mid+1,r); pushup(a); return a; } int rot[N]; void calc(int u,int fa) { for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==fa) continue; calc(v,u); rot[u]=merge(rot[u],rot[v],1,100000); } ans[u]=tr[rot[u]].tre; if(tr[rot[u]].sum==0) ans[u]=0; } }st; int main() { cin>>n>>m; for(int i=1;i<n;i++) { int u,v;cin>>u>>v; add(u,v),add(v,u); } dfs1(1,0),dfs2(1,1); for(int i=1;i<=m;i++) { int u,v,w; cin>>u>>v>>w; int lc=lca(u,v); st.change(st.rot[u],1,100000,w,1); st.change(st.rot[v],1,100000,w,1); st.change(st.rot[lc],1,100000,w,-1); st.change(st.rot[f[lc]],1,100000,w,-1); } st.calc(1,0); for(int i=1;i<=n;i++) cout<<ans[i]<<endl; }
例题5 P3224 [HNOI2012] 永无乡
芝士模板。
但是又有玄学操作!
用并查集维护联通块。用动态开点权值线段树和线段树合并维护区间第 大。
因为查询第 大的代码参数写反了,一直跑不出来。。。。。
#include<bits/stdc++.h> using namespace std; const int N=100010; int f[N]; int find(int x) { if(x!=f[x]) f[x]=find(f[x]); return f[x]; } int ccnt=1;int num[N]; struct SegTrMerge{ struct node{ int ls,rs,sum,id; }tr[N*30]; #define lid tr[now].ls #define rid tr[now].rs void pushup(int now){tr[now].sum=tr[lid].sum+tr[rid].sum;} void change(int &now,int l,int r,int x) { if(!now) now=++ccnt; tr[now].sum++; if(l==r) return ; int mid=(l+r)>>1; if(x<=mid) change(lid,l,mid,x); else change(rid,mid+1,r,x); } int query(int now,int l,int r,int k) { if(tr[now].sum<k||!now) return 0; if(l==r) return num[l]; int mid=(l+r)>>1; if(k<=tr[lid].sum) return query(lid,l,mid,k); else return query(rid,mid+1,r,k-tr[lid].sum); } void merge(int &a,int b) { if(!a){a=b;return ;} if(!b) return ; tr[a].sum+=tr[b].sum; merge(tr[a].ls,tr[b].ls); merge(tr[a].rs,tr[b].rs); } }st; int root[N]; int main() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { int x; cin>>x;f[i]=i;num[x]=i; st.change(root[i],1,n,x); } for(int i=1;i<=m;i++) { int u,v; cin>>u>>v; u=find(u),v=find(v); f[v]=u; st.merge(root[u],root[v]); } int q;cin>>q; while(q--) { string c;cin>>c; int u,v; cin>>u>>v; if(c[0]=='Q') { u=find(u); if(st.tr[root[u]].sum<v) { cout<<-1<<endl; continue; } cout<<st.query(root[u],1,n,v)<<endl; } else { u=find(u),v=find(v); if(u==v) continue; f[v]=u; st.merge(root[u],root[v]); } } }
例题6 P4219 [BJOI2014] 大融合
把它放到例题里面纯粹是因为在波波的训练里头。
我是参考洛谷的一篇树剖解子做的。
用树剖+并查集来想就会非常简单。
首先把操作离线,建一个操作里叙述的森林,
然后建一个虚拟根节点,把森林连成一棵树。(也可以直接使用 )
跑树剖代码,把树状数组初始化一下,清空并查集。
然后对于一个操作:
- 操作 :
把 到 的路径上每个点都加上 - 操作 :
如果 是 的父节点,那么答案就是
query(x)
表示当前 的子树大小。
#include<bits/stdc++.h> using namespace std; int n,q; const int N=1e5+2; int f[N]; int find(int x) { if(x!=f[x]) f[x]=find(f[x]); return f[x]; } struct operation{ int tp,x,y; }op[N]; struct node{ int to,next; }edge[N<<1]; int head[N],cnt; void add(int u,int v) { edge[++cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt; } void read() { cin>>n>>q; for(int i=1;i<=n;i++) f[i]=i; for(int i=1;i<=q;i++) { getchar(); char c=getchar(); if(c=='A') op[i].tp=1; else op[i].tp=0; cin>>op[i].x>>op[i].y; if(op[i].tp) { add(op[i].x,op[i].y),add(op[i].y,op[i].x); f[find(op[i].y)]=find(op[i].x); } } } int tim,top[N],id[N],siz[N],son[N],dep[N],father[N]; void dfs1(int u) { siz[u]=1; for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==father[u]) continue; father[v]=u; dep[v]=dep[u]+1; dfs1(v); siz[u]+=siz[v]; if(siz[son[u]]<siz[v]) son[u]=v; } } void dfs2(int u,int t) { top[u]=t,id[u]=++tim; if(!son[u]) return ; dfs2(son[u],t); for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==father[u]||v==son[u]) continue; dfs2(v,v); } } int lowbit(int x){return x&-x;} int c[N]; void update(int x,int k){ for(;x<=n;x+=lowbit(x))c[x]+=k; } int query(int x) { int res=0; for(;x>0;x-=lowbit(x)) res+=c[x]; return res; } int addpath(int u,int v,int k) { while(top[u]!=top[v]) { update(id[top[u]],k),update(id[u]+1,-k); u=father[top[u]]; } update(id[v],k),update(id[u]+1,-k); } int main() { read(); for(int i=1;i<=n;i++) { if(find(i)==i) add(i,n+1),add(n+1,i);//连森林成一棵树 } n++;//玄学 dfs1(n),dfs2(n,n+1); update(1,1); for(int i=1;i<=n;i++) f[i]=i;//cout<<c[i]<<" "; for(int i=1;i<=q;i++) { int x=op[i].x,y=op[i].y; if(f[x]==y) swap(x,y); if(op[i].tp==1) { addpath(x,find(x),query(id[y]));//cout<<"add\n"; f[find(y)]=find(x); } else { long long ans=0; int s=query(id[find(x)]),s1=query(id[y]); ans=(s-s1)*s1; cout<<ans<<endl; } } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析