【知识点】线段树相关算法
线段树合并:
一般是将若干棵权值线段树的信息整合到一棵权值线段树上。均摊复杂度$O(n\log{n})$。
同时遍历两棵线段树,若某一边没有节点则直接返回另一边的节点,否则继续遍历直到$l=r$。
#include<bits/stdc++.h> #define maxn 1000005 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define rint register int #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; int to[maxn<<1],nxt[maxn<<1],hd[maxn],F[maxn][20]; int tr[maxn<<4],mx[maxn<<4],ls[maxn<<4],rs[maxn<<4]; int ans[maxn],rt[maxn],cnt,tot,dep[maxn],N=100000; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline void addedge(int u,int v){ to[++cnt]=v,nxt[cnt]=hd[u],hd[u]=cnt; to[++cnt]=u,nxt[cnt]=hd[v],hd[v]=cnt; } inline void dfs(int u,int fa){ F[u][0]=fa,dep[u]=dep[fa]+1; for(int i=1;i<20;i++) F[u][i]=F[F[u][i-1]][i-1]; for(int i=hd[u];i;i=nxt[i]){ int v=to[i]; if(v!=fa) dfs(v,u); } } inline int lca(int u,int v){ if(dep[u]>dep[v]) swap(u,v); for(int i=19;i>=0;i--) if(dep[F[v][i]]>=dep[u]) v=F[v][i]; if(u==v) return u; for(int i=19;i>=0;i--) if(F[u][i]!=F[v][i]) u=F[u][i],v=F[v][i]; return F[u][0]; } inline void pushup(int k){ if(tr[ls[k]]>=tr[rs[k]]) tr[k]=tr[ls[k]],mx[k]=mx[ls[k]]; else tr[k]=tr[rs[k]],mx[k]=mx[rs[k]]; } inline int add(int x,int y,int l,int r,int k){ if(!k) k=++tot; if(l==r){tr[k]+=y,mx[k]=l;return k;} int mid=l+r>>1; if(x<=mid) ls[k]=add(x,y,l,mid,ls[k]); else rs[k]=add(x,y,mid+1,r,rs[k]); pushup(k); return k; } inline int merge(int l,int r,int u,int v){ if(!u || !v) return u|v; if(l==r){tr[u]+=tr[v],mx[u]=l;return u;} int mid=l+r>>1; ls[u]=merge(l,mid,ls[u],ls[v]); rs[u]=merge(mid+1,r,rs[u],rs[v]); pushup(u); return u; } inline void Dfs(int u,int fa){ for(int i=hd[u];i;i=nxt[i]){ int v=to[i]; if(v==fa) continue; Dfs(v,u); rt[u]=merge(1,N,rt[u],rt[v]); } ans[u]=(tr[rt[u]]==0)?0:mx[rt[u]]; } int main(){ int n=read(),m=read(); for(int i=1;i<n;i++){ int u=read(),v=read(); addedge(u,v); } dfs(1,0); for(int i=1;i<=m;i++){ int u=read(),v=read(),w=read(),l=lca(u,v); rt[u]=add(w,1,1,N,rt[u]); rt[v]=add(w,1,1,N,rt[v]); rt[l]=add(w,-1,1,N,rt[l]); rt[F[l][0]]=add(w,-1,1,N,rt[F[l][0]]); } Dfs(1,0); for(int i=1;i<=n;i++) cout<<ans[i]<<endl; return 0; }
线段树分治:
大概是给你若干个只在时间$[l,r]$内生效的操作,求每个时间的状态。
那么以时间为下标建立线段树,把每个操作视为区间操作。
一般会写成标记永久化的形式。复杂度$O(n\log{n})$。
#include<bits/stdc++.h> #define maxn 500005 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define rint register ll #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; ll ans[maxn],tr[maxn<<2],num[maxn<<2]; ll L[maxn],R[maxn],val[maxn],n,mod; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline void build(int l,int r,int k){ tr[k]=1; if(l==r) return; int mid=l+r>>1; build(l,mid,k<<1),build(mid+1,r,k<<1|1); } inline void mul(ll x,ll y,ll z,ll l,ll r,ll k){ if(x<=l && r<=y){tr[k]=tr[k]*z%mod;return;} ll mid=l+r>>1; if(x<=mid) mul(x,y,z,l,mid,k<<1); if(y>mid) mul(x,y,z,mid+1,r,k<<1|1); } inline void solve(ll l,ll r,ll v,ll k){ num[k]=tr[k]*v%mod; if(l==r){ans[l]=num[k];return;} ll mid=l+r>>1; solve(l,mid,num[k],k<<1); solve(mid+1,r,num[k],k<<1|1); } int main(){ ll T=read(); while(T--){ n=read(),mod=read(); for(ll i=1;i<=n;i++){ ll op=read(),m=read(); if(op==1) L[i]=i,R[i]=n,val[i]=m; else L[i]=R[i]=val[i]=0,R[m]=i-1; } build(1,n,1); for(ll i=1;i<=n;i++){ //cout<<L[i]<<" "<<R[i]<<" "<<val[i]<<endl; if(val[i]) mul(L[i],R[i],val[i],1,n,1); } solve(1,n,1,1); for(ll i=1;i<=n;i++) printf("%lld\n",ans[i]); } return 0; }