动态开点权值线段树
动态开点线段树,只有需要用到一个点才新建该点,否则不进行构建,可以节省空间复杂度。
class SegmentTree{ struct tree{ int l,r,sz; }t[N<<2]; int tot,s[N<<2],root[N],rtc=1,L=-1e7,R=1e7; #define l(p) (t[p].l) #define r(p) (t[p].r) #define s(p) (t[p].sz) inline int create(){/*新建节点*/ return s[0]?s[s[0]--]:++tot; } inline void recycle(int p){/*内存回收*/ t[s[++s[0]]=p]={0,0,0}; } inline void pushup(int p){ s(p)=s(l(p))+s(r(p)); } void update(int&p,int l,int r,int x,int v){/*给权值x的数量加上v*/ if(!p)p=create(); if(l==r)return s(p)+=v,void(); int mid=l+r>>1; if(x<=mid)update(l(p),l,mid,x,v); else update(r(p),mid+1,r,x,v); pushup(p); } int query(int p,int l,int r,int x,int y){/*查询权值在[x,y]之间的数的个数*/ if(!p)return 0; if(x<=l&&r<=y)return s(p); int mid=l+r>>1,re=0; if(x<=mid)re+=query(l(p),l,mid,x,y); if(mid<y)re+=query(r(p),mid+1,r,x,y); return re; } void merge(int&x,int y){/*将x和y两颗线段树合并为x*/ if(!x||!y)return x|=y,void(); s(x)+=s(y); merge(l(x),l(y)); merge(r(x),r(y)); recycle(y); } void merge(int&p,int x,int y){/*将x和y两颗线段树合并为p*/ if(!x||!y)return p=x|y,void();/*若其中一个为0,即某个权值线段树上没有这个点,那么无需合并,直接返回*/ s(p=x)+=s(y);/*信息整合*/ merge(l(p),l(p),l(y)); merge(r(p),r(p),r(y)); recycle(y);/*回收*/ } void split(int p,int&x,int k){/*将可重集p的前k小元素分裂成p和x*/ if(!p)return; x=create(); int re=s(l(p)); if(k>re)split(r(p),r(x),k-re);/*若p的左子树大小<k则递归右侧,要控制k!=0,即k!=re*/ else swap(r(p),r(x));/*k>=左子树大小时,右子树均归为x*/ if(k<re)split(l(p),l(x),k);/*递归左侧*/ s(x)=s(p)-k;/*分裂出去的可重集x的权值就是本来全部的p的权值-k*/ s(p)=k;/*将p的权值更新为k*/ } int kth(int p,int l,int r,int k){ if(!p)return -1; if(l==r)return l;/*只剩一个节点即为答案*/ int mid=l+r>>1; if(s(l(p))>=k)return kth(l(p),l,mid,k);/*左子树>=k,递归左子树*/ else return kth(r(p),mid+1,r,k-s(l(p)));/*递归右子树,k只减去左子树的大小即可*/ } public: inline void reset(int l,int r){ L=l,R=r; } inline void cut(int p,int l,int r){/*将可重集p中值域[l,r]之间的数分离成一个新的可重集*/ int a=query(root[p],L,R,1,r),b=query(root[p],L,R,l,r),x; split(root[p],root[++rtc],a-b);/*先从p中分离出[1,r-l]*/ split(root[rtc],x,b);/*再分离出[r-l+1,r]*/ merge(root[p],x);/*将[1,r-l]和[r,n]进行合并*/ } inline void copy(int p,int x){/*将可重集x合并入可重集p中*/ merge(root[p],root[x]); } inline int count(int p,int l,int r){/*查询可重集p中值域[l,r]之间的数的个数*/ return query(root[p],L,R,l,r); } inline void insert(int p,int x,int v){/*在可重集p中插入v个x*/ update(root[p],L,R,x,v); } inline int kth(int p,int k){/*查询可重集p中的第k小元素*/ return s(root[p])<k?-1:kth(root[p],L,R,k); } inline int rank(int p,int x){ return query(root[p],L,R,L,x-1)+1; } inline int pre(int p,int x){ return kth(root[p],L,R,query(root[p],L,R,L,x-1)); } inline int suc(int p,int x){ return kth(root[p],L,R,query(root[p],L,R,L,x)+1); } }seg;
询问每个点子树内大于根节点权值的数的数量。普通区间和线段树,dfs时线段树合并统计答案即可。
void dfs(int x){ for(auto y:v[x]){ dfs(y); s.merge(root[x],root[y]); } ans[x]=s.query(root[x],1,1e9,a[x]+1,1e9); }
一棵树上,每次在(x,y)的路径上每个点放上一个z类型的救济粮,问最后每个点最多的救济粮种类。每个点维护一棵权值线段树,下标为救济粮种类,区间和维护最多的救济粮的数量,查询时若数量为0,则ans标记为0,自底向上做树上前缀和与线段树合并,同时要树上差分。
#include<bits/stdc++.h> using namespace std; const int N=1e5+5; int f[N][25],dep[N],root[N],ans[N]; vector<int>v[N]; struct SegmentTree{ struct tree{ int l,r,sum,id; }t[N*80]; int tot,s[N*80]; #define l(p) (t[p].l) #define r(p) (t[p].r) #define s(p) (t[p].sum) #define i(p) (t[p].id) inline int create(){ return s[0]?s[s[0]--]:++tot; } inline void recycle(int p){ t[s[++s[0]]=p]={0,0,0,0}; } inline void pushup(int p){ if(s(l(p))>=s(r(p))){ s(p)=s(l(p)); i(p)=i(l(p)); } else{ s(p)=s(r(p)); i(p)=i(r(p)); } } void update(int&p,int l,int r,int x,int v){ if(!p)p=create(); if(l==r)return s(p)+=v,i(p)=x,void(); int mid=l+r>>1; if(x<=mid)update(l(p),l,mid,x,v); else update(r(p),mid+1,r,x,v); pushup(p); } void merge(int&x,int y,int l,int r){ if(!x||!y)return x|=y,void(); if(l==r)return s(x)+=s(y),void(); int mid=l+r>>1; merge(l(x),l(y),l,mid); merge(r(x),r(y),mid+1,r); pushup(x); recycle(y); } }s; void dfs(int x,int fa){ dep[x]=dep[fa]+1; f[x][0]=fa; for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1]; for(auto y:v[x])if(y^fa)dfs(y,x); } inline int LCA(int x,int y){ if(dep[x]<dep[y])swap(x,y); for(int i=20;~i;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i]; if(x==y)return x; for(int i=20;~i;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i]; return f[x][0]; } void calc(int x,int fa){ for(auto y:v[x]){ if(y==fa)continue; calc(y,x); s.merge(root[x],root[y],1,N-1); } ans[x]=s.t[root[x]].id; if(!s.t[root[x]].sum)ans[x]=0; } int main(){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int n,m; cin>>n>>m; for(int i=1;i<n;i++){ int a,b; cin>>a>>b; v[a].push_back(b); v[b].push_back(a); } dfs(1,0); while(m--){ int a,b,c; cin>>a>>b>>c; int t=LCA(a,b); s.update(root[a],1,N-1,c,1); s.update(root[b],1,N-1,c,1); s.update(root[t],1,N-1,c,-1); s.update(root[f[t][0]],1,N-1,c,-1); } calc(1,0); for(int i=1;i<=n;i++)cout<<ans[i]<<'\n'; return 0; }
在两点间连边和询问一条边上的负载,一条边的负载就是所在能够联通的树上通过它的简单路径的数量。负载也就是一条边两边联通快大小的乘积,离线建立森林,每个点维护线段树的dfs序编号,加边则线段树合并,这样x或y的子树编号连续。
#include<bits/stdc++.h> using namespace std; const int N=1e6+6; struct SegmentTree{ struct tree{ int l,r,sz; }t[N<<2]; int tot,s[N<<2]; #define l(p) (t[p].l) #define r(p) (t[p].r) #define s(p) (t[p].sz) inline int create(){ return s[0]?s[s[0]--]:++tot; } inline void recycle(int p){ t[s[++s[0]]=p]={0,0,0}; } inline void pushup(int p){ s(p)=s(l(p))+s(r(p)); } void update(int&p,int l,int r,int x,int v){ if(!p)p=create(); if(l==r)return s(p)+=v,void(); int mid=l+r>>1; if(x<=mid)update(l(p),l,mid,x,v); else update(r(p),mid+1,r,x,v); pushup(p); } int query(int p,int l,int r,int x,int y){ if(x<=l&&r<=y)return s(p); int mid=l+r>>1,re=0; if(x<=mid)re+=query(l(p),l,mid,x,y); if(mid<y)re+=query(r(p),mid+1,r,x,y); return re; } void merge(int&x,int y,int l,int r){ if(!x||!y)return x|=y,void(); if(l==r)return s(x)+=s(y),void(); int mid=l+r>>1; merge(l(x),l(y),l,mid); merge(r(x),r(y),mid+1,r); pushup(x); recycle(y); } }s; struct DSU{ int f[N],sz[N]; inline void init(int n){ for(int i=1;i<=n;i++)f[i]=i; } int find(int x){ return x==f[x]?x:f[x]=find(f[x]); } inline void merge(int x,int y){ x=find(x),y=find(y); if(x==y)return; if(sz[x]<sz[y])x^=y^=x^=y; f[y]=x,sz[x]+=sz[y]; } }d; int ipt[N],opt[N],dfn,dep[N],root[N],n,q; char ch[3]; vector<int>v[N]; void dfs(int x,int fa){ dep[x]=dep[fa]+1; s.update(root[x],1,n,ipt[x]=++dfn,1); for(auto y:v[x])if(y^fa)dfs(y,x); opt[x]=dfn; } struct node{ int x,y,f; }ask[N]; int main(){ cin>>n>>q; for(int i=1;i<=q;i++){ int x,y; cin>>ch>>x>>y; ask[i]={x,y,0}; if(ch[0]=='A')ask[i].f=1,v[x].push_back(y),v[y].push_back(x); } d.init(n); dfs(1,0); for(int i=1;i<=q;i++){ if(ask[i].f){ int x=d.find(ask[i].x),y=d.find(ask[i].y); s.merge(root[x],root[y],1,n); d.merge(x,y); } else{ int x=ask[i].x,y=ask[i].y; if(dep[x]<dep[y])x^=y^=x^=y; int a=d.find(x),b=s.query(root[a],1,n,ipt[x],opt[x]); cout<<b*(s.t[root[a]].sz-b)<<'\n'; } } return 0; }
维护所谓的动态图,操作1新建一个权值为x的节点,操作2连接两个点,操作3将点x所属联通块内所有点的权值对y取max,操作4将点x所属联通块内所有点的权值对y取min,操作5询问点x所属联通块内的第k小,操作6询问点x和点y所属两个联通块内所有点权值之积的大小,操作7询问点x所在联通块内点的数量。对于乘法操作,取log来转变成加法操作,并查集维护联通块。
#include<bits/stdc++.h> using namespace std; const int N=1e7+7; struct SegmentTree{ struct tree{ int l,r,sz; bool del; double v; }t[N]; int tot; #define l(p) (t[p].l) #define r(p) (t[p].r) #define s(p) (t[p].sz) #define d(p) (t[p].del) #define v(p) (t[p].v) inline void pushup(int p){ s(p)=s(l(p))+s(r(p)); v(p)=v(l(p))+v(r(p)); } inline void pushdown(int p){ if(!d(p))return; d(p)=s(l(p))=s(r(p))=v(l(p))=v(r(p))=0; d(l(p))=d(r(p))=1; } void update(int&p,int l,int r,int x,int v,double w){ if(!p)p=++tot; if(l==r)return s(p)+=v,v(p)+=v*w,void(); pushdown(p); int mid=l+r>>1; if(x<=mid)update(l(p),l,mid,x,v,w); else update(r(p),mid+1,r,x,v,w); pushup(p); } void del(int p,int l,int r,int x,int y){ if(!p)return; if(x<=l&&r<=y)return s(p)=v(p)=0,d(p)=1,void(); pushdown(p); int mid=l+r>>1; if(x<=mid)del(l(p),l,mid,x,y); if(mid<y)del(r(p),mid+1,r,x,y); pushup(p); } int query(int p,int l,int r,int x,int y){ if(!p)return 0; if(x<=l&&r<=y)return s(p); pushdown(p); int mid=l+r>>1,re=0; if(x<=mid)re+=query(l(p),l,mid,x,y); if(mid<y)re+=query(r(p),mid+1,r,x,y); return re; } void merge(int&x,int y){ if(!x||!y)return x|=y,void(); s(x)+=s(y); v(x)+=v(y); pushdown(x); pushdown(y); merge(l(x),l(y)); merge(r(x),r(y)); } int kth(int p,int l,int r,int k){ if(l==r)return l; pushdown(p); int mid=l+r>>1; if(k<=s(l(p)))return kth(l(p),l,mid,k); else return kth(r(p),mid+1,r,k-s(l(p))); } }s; int f[N],n,root[N],m=2147483646; int find(int x){ return x==f[x]?x:f[x]=find(f[x]); } int main(){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int q; cin>>q; for(int i=1;i<=q;i++){ int op,x,y,re=0; cin>>op>>x; if(op!=1&&op!=7)cin>>y; switch(op){ case 1:s.update(root[++n],1,m,x,1/*数量*/,log(x)/*自己的对数,得到乘积*/),f[n]=n;break; case 2:x=find(x),y=find(y);if(x!=y)s.merge(root[f[y]=x],root[y]);/*线段树合并进行连边*/break; case 3:re=s.query(root[x=find(x)],1,m,1,y);/*查询联通块内<=y的数的数量*/s.del(root[x],1,m,1,y)/*将这些数删掉*/,s.update(root[x],1,m,y,re,log(y))/*重新加入相同数量个y*/;break; case 4:re=s.query(root[x=find(x)],1,m,y,m);/*查询联通块内权值在[y,m]内的数的数量*/s.del(root[x],1,m,y,m)/*将这些数删掉*/,s.update(root[x],1,m,y,re,log(y))/*重新加入相同数量个y*/;break; case 5:cout<<s.kth(root[find(x)],1,m,y)<<'\n';break; case 6:cout<<(s.t[root[find(x)]].v>s.t[root[find(y)]].v)<<'\n';break; case 7:cout<<(s.t[root[find(x)]].sz)<<'\n';break; } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】