线段树合并[学习笔记]
线段树合并
壹.
什么是线段树合并? 简单来说就是合并两棵线段树
对于当前要合并的节点 \(x,y\)
-
如果一方为空返回另一方
-
否则分别合并左右子树
int merge(int x,int y){ if(!x||!y) return x+y; cnt(x)+=cnt(y); //... ls(x)=merge(ls(x),ls(y)); rs(x)=merge(rs(x),rs(y)); return x; }
有时候需要到叶子结点再合并,然后 \(pushup\)
int merge(int x,int y,int l,int r){ if(!x||!y) return x+y; if(l==r){ cnt(x)+=cnt(y); //... return x; } int mid=(l+r)>>1; ls(x)=merge(ls(x),ls(y),l,mid); rs(x)=merge(rs(x),rs(y),mid+1,r); pushup(x); return x; }
大概就是这样。
我们多数情况是在动态开点线段树中使用线段树合并,动态开点,就是非静态地开一些点, 就是一棵缺胳膊少腿的线段树,我们需要用哪个节点,再现开,很节省空间。
嗯,很是简单,对吧,但是——
线段树合并题的难点从来不在线段树合并上(我口胡的)
贰.\(hs\)题单
\(T_A\) 雨天的尾巴
假板子。
对于暴力做法是开 \(O(N\times num)\) 的数组存每个节点存的每个物品的个数,最后每个点 \(O(num)\) 遍历求答案。这无疑会浪费很多时间空间,那么动态开点权值线段树就是处理这种情况的有力武器( \(num\) 为物品种数)
那么树上差分,最后跑子树求和的时候把子节点合并到父亲上,\(pushup\) 统计答案。
!!!警示后人
线段树合并要"及时行乐",合并完当前节点就赶紧存储答案,因为把它往上合并时他的信息会发生改变,因为
if(!x||!y) return x+y;
,我在这卡了半天,不要到最后再输出每个节点的答案。
$CODE$
#include<bits/stdc++.h> using namespace std; #define read read() #define pt puts("") #define swap(x,y) (x^=y,y^=x,x^=y) 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<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } const int N = 1e5+10; int n,m; int ans[N]; int qu[N],qv[N],z[N],num; int lsh[N]; namespace GETLCA{ struct EDGE{int next,to;}e[N<<1]; int head[N],tot; void add(int u,int v){e[++tot]={head[u],v};head[u]=tot;} int fa[N][18],depth[N]; int lg2[N]; void dfs(int x){ depth[x]=depth[fa[x][0]]+1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x][0]) continue; fa[y][0]=x;dfs(y); } } void init(){ for(int i=2;i<=n;i++) lg2[i]=lg2[i>>1]+1; for(int j=1;j<=lg2[n];j++) for(int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; } int LCA(int u,int v){ if(depth[u]<depth[v]) swap(u,v); int k=lg2[depth[u]-depth[v]]; for(int i=k;i>=0;i--) if(depth[fa[u][i]]>=depth[v]) u=fa[u][i]; if(u==v) return u; k=lg2[depth[u]]; for(int i=k;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i]; return fa[u][0]; } }using namespace GETLCA; namespace Segment_Tree{ struct Tree{ int ls,rs,cnt,id; #define ls(i) tr[i].ls #define rs(i) tr[i].rs #define cnt(i) tr[i].cnt #define id(i) tr[i].id }tr[5000000]; int root[N];int sum; void pushup(int i){ cnt(i)=max(cnt(ls(i)),cnt(rs(i))); id(i)=cnt(ls(i))>=cnt(rs(i))?id(ls(i)):id(rs(i)); } void create(int &i,int l,int r,int x,int k){ if(!i) i=++sum; if(l==r){ cnt(i)+=k; id(i)=cnt(i)?l:0; return; } int mid=(l+r)>>1; if(x<=mid) create(ls(i),l,mid,x,k); else create(rs(i),mid+1,r,x,k); pushup(i); } int merge(int x,int y,int l,int r){ if(!x||!y) return x+y; if(l==r){ cnt(x)+=cnt(y); id(x)=cnt(x)?l:0; return x; } int mid=(l+r)>>1; ls(x)=merge(ls(x),ls(y),l,mid); rs(x)=merge(rs(x),rs(y),mid+1,r); pushup(x); return x; } void solve(int x){ for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x][0]) continue; solve(y); root[x]=merge(root[x],root[y],1,num); } ans[x]=id(root[x]);//及时存答案! } }using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,m=read; for(int i=1,a,b;i<n;i++){ a=read,b=read;add(a,b);add(b,a); } dfs(1);init(); for(int i=1;i<=m;i++) qu[i]=read,qv[i]=read,lsh[i]=z[i]=read; sort(lsh+1,lsh+m+1);num=unique(lsh+1,lsh+m+1)-lsh-1; for(int i=1;i<=m;i++) z[i]=lower_bound(lsh+1,lsh+num+1,z[i])-lsh; for(int i=1;i<=m;i++){ int u=qu[i],v=qv[i]; int lca=LCA(u,v); create(root[u],1,num,z[i],1); create(root[v],1,num,z[i],1); create(root[lca],1,num,z[i],-1); create(root[fa[lca][0]],1,num,z[i],-1); } solve(1); for(int i=1;i<=n;i++) write(lsh[ans[i]]),pt; return 0; }
\(T_B\) bzoj4636蒟蒻的数列
值域很大,考虑动态开点,然后从左右儿子更新答案,貌似不用线段树合并
$CODE$
#include<bits/stdc++.h> using namespace std; #define int long long #define read read() #define pt puts("") 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<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 40010 #define m 1000000000 int n; int ans; namespace SegmentTree{ struct Tree{ int ls,rs,k; #define ls(i) tr[i].ls #define rs(i) tr[i].rs #define k(i) tr[i].k }tr[10000000]; int root; int tot; void create(int &i,int l,int r,int L,int R,int k){ if(l>R||r<L) return; if(!i) i=++tot; if(L<=l&&r<=R){ k(i)=max(k(i),k); return; } int mid=(l+r)>>1; create(ls(i),l,mid,L,R,k); create(rs(i),mid+1,r,L,R,k); return; } void query(int i,int l,int r){ int mid=(l+r)>>1; if(!ls(i)) ans+=(mid-l+1)*k(i); if(!rs(i)) ans+=(r-mid)*k(i); if(ls(i)) k(ls(i))=max(k(ls(i)),k(i)); if(rs(i)) k(rs(i))=max(k(rs(i)),k(i)); if(ls(i)) query(ls(i),l,mid); if(rs(i)) query(rs(i),mid+1,r); return; } }using namespace SegmentTree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read; int l,r,k; for(int i=1;i<=n;i++){ l=read,r=read-1,k=read; if(l>r) continue; create(root,1,m,l,r,k); } query(root,1,m); write(ans); return 0; }
\(T_C\) Promotion Counting
这才是真板子。
每个节点开一棵权值线段树,从下往上合并,查询比该节点的能力值大的牛个数即可。
$CODE$
#include<bits/stdc++.h> using namespace std; #define read read() #define pt puts("") 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<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 100010 int n; int p[N]; int t[N]; void LSH(){ sort(t+1,t+n+1); int tt=unique(t+1,t+n+1)-t-1; for(int i=1;i<=n;i++) p[i]=lower_bound(t+1,t+tt+1,p[i])-t; } struct Edge{int next,to;}e[N<<1]; int tot,head[N]; void add(int u,int v){ e[++tot]={head[u],v}; head[u]=tot; } int ans[N]; namespace SegmentTree{ int sum; int root[N]; struct Tree{ int ls,rs; int L,R; int pro; #define ls(i) tr[i].ls #define rs(i) tr[i].rs #define L(i) tr[i].L #define R(i) tr[i].R #define pro(i) tr[i].pro }tr[100000000]; void create(int &rt,int x,int l,int r){ if(!rt) rt=++sum; L(rt)=l,R(rt)=r; if(l==r){ pro(rt)=1; return; } int mid=(l+r)>>1; if(x<=mid) create(ls(rt),x,l,mid); else create(rs(rt),x,mid+1,r); pro(rt)=pro(ls(rt))+pro(rs(rt)); } int merge(int x,int y){ if(!x||!y) return x+y; pro(x)+=pro(y); ls(x)=merge(ls(x),ls(y)); rs(x)=merge(rs(x),rs(y)); return x; } int ask(int rt,int l,int r){ if(!rt) return 0; if(l<=L(rt)&&R(rt)<=r) return pro(rt); int res=0,mid=(L(rt)+R(rt))>>1; if(l<=mid) res+=ask(ls(rt),l,r); if(r>mid) res+=ask(rs(rt),l,r); return res; } void dfs(int x){ for(int i=head[x];i;i=e[i].next){ int y=e[i].to; dfs(y); root[x]=merge(root[x],root[y]); } ans[x]=ask(root[x],p[x]+1,n); } }using namespace SegmentTree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read; for(int i=1;i<=n;i++) t[i]=p[i]=read; LSH(); for(int i=1;i<=n;i++){ create(root[i],p[i],1,n); } for(int v=2;v<=n;v++){ int u=read;add(u,v); } dfs(1); for(int i=1;i<=n;i++) write(ans[i]),pt; return 0; }
\(T_D\) 永无乡
怎么判断两个岛的联通情况?并查集维护。
-
对于
B
操作,合并两个点所在并查集对应的权值线段树。 -
对于
Q
操作,查询该并查集内第 \(k\) 大点,从节点 \(i\) 出发(如果有解的话):- 如果 \(ls(i)\) 的点数大于 \(k\),则第 \(k\) 大点一定在左子树内,递归查询。
- 否则就是在右子树。
$CODE$
#include<bits/stdc++.h> using namespace std; #define read read() #define pt puts("") 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<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 100010 int n,m; int w[N],id[N]; int fa[N]; int find(int x){ if(fa[x]==x) return x; return fa[x]=find(fa[x]); } namespace SegmentTree{ struct Tree{ int ls,rs,son; #define ls(i) tr[i].ls #define rs(i) tr[i].rs #define son(i) tr[i].son }tr[10000000]; int cnt; int root[N]; void create(int &i,int l,int r,int x){ if(!i) i=++cnt; ++son(i); if(l==r) return; int mid=(l+r)>>1; if(x<=mid) create(ls(i),l,mid,x); else create(rs(i),mid+1,r,x); return; } int merge(int x,int y){ if(!x||!y) return x+y; son(x)+=son(y); ls(x)=merge(ls(x),ls(y)); rs(x)=merge(rs(x),rs(y)); return x; } int ask(int i,int l,int r,int k){ if(l==r) return id[l]; int mid=(l+r)>>1; if(son(ls(i))>=k) return ask(ls(i),l,mid,k); else return ask(rs(i),mid+1,r,k-son(ls(i))); } }using namespace SegmentTree; void link(int x,int y){ x=find(x);y=find(y); if(x==y) return; root[x]=merge(root[x],root[y]); fa[y]=fa[x]; } int ans; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,m=read; for(int i=1;i<=n;i++){ w[i]=read;id[w[i]]=i; fa[i]=i; create(root[i],1,n,w[i]); } for(int i=1,a,b;i<=m;i++) a=read,b=read,link(a,b); int T=read,x,y,k; for(int t=1;t<=T;t++){ char op=getchar();while(op!='Q'&&op!='B')op=getchar(); if(op=='B') x=read,y=read,link(x,y); else{ x=read,k=read;x=find(x); if(son(root[x])<k) puts("-1"); else write(ask(root[x],1,n,k)),pt; } } return 0; }
\(T_E\) 魔法少女LJJ
此题不难,但是很烦。
- 操作\(1\),动态开点
- 操作\(2\),并查集维护,线段树合并
- 操作\(3\),从根节点出发,访问左右子树,如果 \(mid<x\),那么左子树显然都要变成 \(0\)(可以直接把左儿子断掉,不用\(pushdown\)),注意这是权值线段树,然后我们用 \(sum\) 记录有多少个点被赋值为 \(0\),再将 \(x\) 处 \(update\) 即可。
- 操作\(4\),同上。
- 操作\(5\),类似永无乡。
- 操作\(6\),小技巧,将 \(a\times b\) 转化为 \(log_2(a\times b)=log_2(a)+log_2(b)\),就不会爆了,值域缩小。但是注意开 \(double\)。
- 操作\(7\),直接查询。
- 操作\(8、9\),线段树分裂(口胡),我们注意到 \(c\leq 7\),出题人无意义行为 \(\dots\)
$CODE$
#include<bits/stdc++.h> using namespace std; #define read read() #define pt puts("") 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<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } const int N=400010; const int M=1e9+5; int m,n; int sum; int fa[N]; int find(int x){ return (fa[x]==x?x:(fa[x]=find(fa[x]))); } namespace Segment_Tree{ struct Tree{ int ls,rs,cnt; double lgsum; #define ls(i) tr[i].ls #define rs(i) tr[i].rs #define cnt(i) tr[i].cnt #define lgsum(i) tr[i].lgsum }tr[5000000]; int rt[N]; int tot; void pushup(int i){ cnt(i)=cnt(ls(i))+cnt(rs(i)); lgsum(i)=lgsum(ls(i))+lgsum(rs(i)); } void create(int &i,int l,int r,int x){ if(!i) i=++tot; if(l==r){ cnt(i)++; lgsum(i)=1.0*cnt(i)*log2(l); return; } int mid=(l+r)>>1; if(x<=mid) create(ls(i),l,mid,x); else create(rs(i),mid+1,r,x); pushup(i); return; } int merge(int x,int y,int l,int r){ if(!x||!y) return x+y; if(l==r){ cnt(x)+=cnt(y); lgsum(x)=1.0*cnt(x)*log2(l); return x; } int mid=(l+r)>>1; ls(x)=merge(ls(x),ls(y),l,mid); rs(x)=merge(rs(x),rs(y),mid+1,r); pushup(x); return x; } void link(int a,int b){ int A=find(a);int B=find(b); if(A==B) return; rt[A]=merge(rt[A],rt[B],1,M); fa[B]=A; } void modify1(int i,int l,int r,int x){ if(l==r) return; int mid=(l+r)>>1; if(mid<x){ sum+=cnt(ls(i));ls(i)=0; modify1(rs(i),mid+1,r,x); } else modify1(ls(i),l,mid,x); pushup(i); } void modify2(int i,int l,int r,int x){ if(l==r) return; int mid=(l+r)>>1; if(mid>x){ sum+=cnt(rs(i));rs(i)=0; modify2(ls(i),l,mid,x); } else modify2(rs(i),mid+1,r,x); pushup(i); } void update(int &i,int l,int r,int x){ if(!i) i=++tot; if(l==r){ cnt(i)+=sum; lgsum(i)=1.0*cnt(i)*log2(x); return; } int mid=(l+r)>>1; if(x<=mid) update(ls(i),l,mid,x); else update(rs(i),mid+1,r,x); pushup(i); } int ask(int i,int l,int r,int k){ if(l==r) return l; int mid=(l+r)>>1; if(k<=cnt(ls(i))) return ask(ls(i),l,mid,k); return ask(rs(i),mid+1,r,k-cnt(ls(i))); } }using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif m=read; for(int i=1;i<=m;i++) fa[i]=i; int op,x,a,b,k; for(int i=1;i<=m;i++){ op=read; switch(op){ case 1: x=read; create(rt[++n],1,M,x); break; case 2: a=read,b=read; link(a,b); break; case 3: a=read,x=read; a=find(a);sum=0; modify1(rt[a],1,M,x); update(rt[a],1,M,x); break; case 4: a=read,x=read; a=find(a);sum=0; modify2(rt[a],1,M,x); update(rt[a],1,M,x); break; case 5: a=read,k=read; a=find(a); write(ask(rt[a],1,M,k)),pt; break; case 6: a=read,b=read; a=find(a),b=find(b); puts(lgsum(rt[a])>lgsum(rt[b])?"1":"0"); break; case 7: a=read;a=find(a); write(cnt(rt[a])),pt; break; default:break; } } return 0; }
\(T_F\) 大根堆
\(DP\)题,先跑dfs到叶子节点,向上合并时判断是否选父节点,然后线段树上跑二分确定最大值域。
$CODE$
#include<bits/stdc++.h> using namespace std; #define read read() #define pt puts("") 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<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 200010 int n; int v[N],fa[N]; int vv[N],num; struct EDGE{int next,to;}e[N<<1]; int head[N],total; void add(int u,int v){e[++total]={head[u],v};head[u]=total;} namespace Segment_Tree{ struct{ int ls,rs,cnt; #define ls(i) tr[i].ls #define rs(i) tr[i].rs #define cnt(i) tr[i].cnt }tr[5000000]; int rt[N],tot; void create(int &i,int l,int r,int L,int R){ if(!i) i=++tot; if(L<=l&&r<=R){ cnt(i)++; return; } int mid=(l+r)>>1; if(mid>=L) create(ls(i),l,mid,L,R); if(mid<R) create(rs(i),mid+1,r,L,R); return; } int merge(int x,int y,int l,int r){ if(cnt(x)<cnt(y)) swap(x,y); if(!x||!y) return x+y; cnt(x)+=cnt(y); int mid=(l+r)>>1; ls(x)=merge(ls(x),ls(y),l,mid); rs(x)=merge(rs(x),rs(y),mid+1,r); return x; } int ask(int i,int l,int r,int k){ if(!i) return 0; int mid=(l+r)>>1; int res=cnt(i); if(k<=mid) res+=ask(ls(i),l,mid,k); else res+=ask(rs(i),mid+1,r,k); return res; } void dfs(int x){ for(int i=head[x];i;i=e[i].next){ int y=e[i].to; dfs(y); rt[x]=merge(rt[x],rt[y],1,num); } int ans1=ask(rt[x],1,num,v[x]-1)+1; int ans2=ask(rt[x],1,num,v[x]); if(ans1<=ans2) return; int l=v[x],r=num,pos=v[x]; while(l<=r){ int mid=(l+r)>>1; if(ask(rt[x],1,num,mid)<ans1){ l=mid+1;pos=mid; } else r=mid-1; } create(rt[x],1,num,v[x],pos); return; } }using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read; for(int i=1;i<=n;i++){ vv[i]=v[i]=read,fa[i]=read; if(fa[i]) add(fa[i],i); } sort(vv+1,vv+n+1); num=unique(vv+1,vv+n+1)-vv-1; for(int i=1;i<=n;i++) v[i]=lower_bound(vv+1,vv+num+1,v[i])-vv; dfs(1); write(ask(rt[1],1,num,num)); return 0; }
\(T_G\) 领导集团问题
上题多倍经验,贺了 \(shadow\) 的 \(muiltiset\) 做法,比我的做法快了 \(3\) 倍。
https://www.cnblogs.com/The-Shadow-Dragon/p/18169047
$CODE$
#include<bits/stdc++.h> using namespace std; #define read read() #define pt puts("") 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<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 200010 int n; int w[N]; struct EDGE{int next,to;}e[N<<1]; int head[N],total; void add(int u,int v){e[++total]={head[u],v};head[u]=total;} multiset<int>s[N]; multiset<int>::iterator it; void merge(int x,int y){ if(s[x].size()<s[y].size()) swap(s[x],s[y]); for(it=s[y].begin();it!=s[y].end();++it) s[x].insert(*it); s[y].clear(); } void dfs(int x){ for(int i=head[x];i;i=e[i].next){ int y=e[i].to; dfs(y); merge(x,y); } s[x].insert(w[x]); it=s[x].lower_bound(w[x]); if(it!=s[x].begin()) s[x].erase(--it); return; } signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read; for(int i=1;i<=n;i++) w[i]=read; int fa;for(int i=2;i<=n;i++) fa=read,add(fa,i); dfs(1); write(s[1].size()); return 0; }
\(T_H\) 大融合
离线建树操作,跑 \(DFS\) 序。
根据 \(DFS\) 序建权值线段树,并查集维护连通状态。
-
对于
A
操作,合并两个点的并查集和线段树,在线加边。 -
对于
Q
操作,显然我们可以通过深度判断谁是父节点,这里我们令 \((u,v)\),即 \(u\) 是 \(v\) 的父节点,设砍掉 \((u,v)\) 这条边后 \(u\) 所在树大小为 \(sumu\),\(v\)的子树为 \(sumv\),那么答案即为 \(sumu\times sumv\)。我们可以通过 \(DFS\) 序查出 \(v\) 的子树大小和 \(u,v\) 所在整棵树的节点数,答案显然。
$CODE$
#include<bits/stdc++.h> using namespace std; #define swap(x,y) (x^=y,y^=x,x^=y) #define read read() #define pt puts("") #define int long long 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<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 100010 int n,m; struct operate{ int op,u,v; }q[N]; struct EDGE{int next,to;}e[N<<1]; int head[N],total; void add(int u,int v){e[++total]={head[u],v};head[u]=total;} bool exis[N]; int dfn[N],t; int depth[N]; int out[N]; int id[N<<1]; void get_dfn(int x,int pre){ depth[x]=depth[pre]+1; dfn[x]=++t; id[t]=x; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==pre) continue; get_dfn(y,x); } out[x]=t; } int fa[N]; int find(int x){return (fa[x]==x?x:(fa[x]=find(fa[x])));} namespace Segment_Tree{ struct Tree{ int ls,rs,cnt; #define ls(i) tr[i].ls #define rs(i) tr[i].rs #define cnt(i) tr[i].cnt }tr[5000000]; int rt[N],tot; void pushup(int i){ cnt(i)=cnt(ls(i))+cnt(rs(i)); } void create(int &i,int l,int r,int x){ if(!i) i=++tot; if(l==r){ cnt(i)++; return; } int mid=(l+r)>>1; if(x<=mid) create(ls(i),l,mid,x); else create(rs(i),mid+1,r,x); pushup(i); return; } int merge(int x,int y,int l,int r){ if(!x||!y) return x|y; if(l==r){ cnt(x)+=cnt(y); return x; } int mid=(l+r)>>1; ls(x)=merge(ls(x),ls(y),l,mid); rs(x)=merge(rs(x),rs(y),mid+1,r); pushup(x); return x; } int query(int i,int l,int r,int ql,int qr){ if(!i) return 0; if(ql<=l&&r<=qr) return cnt(i); int mid=(l+r)>>1; int res=0; if(ql<=mid) res+=query(ls(i),l,mid,ql,qr); if(qr>mid) res+=query(rs(i),mid+1,r,ql,qr); return res; } } using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,m=read; for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++){ char c=getchar();while(c!='A'&&c!='Q') c=getchar(); q[i].op=(c=='A'?0:1); q[i].u=read,q[i].v=read; exis[q[i].u]=exis[q[i].v]=1; if(c=='A') add(q[i].u,q[i].v),add(q[i].v,q[i].u); } for(int i=1;i<=n;i++){ if(!exis[i]) continue; if(!dfn[i]){ // add(0,i),add(i,0); depth[i]=1; get_dfn(i,0); } } for(int i=1;i<=n;i++){ create(rt[i],1,t,dfn[i]); } for(int i=1;i<=m;i++){ int u=q[i].u,v=q[i].v; if(depth[v]<depth[u]) swap(u,v); int uu=find(u),vv=find(v); if(!q[i].op){ rt[uu]=merge(rt[uu],rt[vv],1,t); fa[vv]=uu; } else{ int sum=cnt(rt[uu]); int sumv=query(rt[uu],1,t,dfn[v],out[v]); int sumu=sum-sumv; write(sumu*sumv);pt; } } return 0; }
\(T_I\) base 基站选址
暂时不会。
DP(口胡)
$CODE$
\(T_J\) Minimax
暂时不会,以后也应该不会。
$CODE$
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战