P5773 [JSOI2016] 轻重路径 题解

P5773 [JSOI2016] 轻重路径 题解


题意简述

在不断删点的情况下动态维护重链剖分的二叉树,求每个重边指向的点的编号之和。


思路分析

法 1:分块思想

\(B\) 次修改中,只有子节点子树大小之差的绝对值 \(\le B\) 的节点有可能更改向下的重边,那我们将满足上述条件的节点建一棵树,每 \(B\) 次暴力重建一次。那么每次建出的树节点数量最多是 \(O(B+\log_2{n})\) 的,证明就略了(看别人题解吧,ahh)。

时间复杂度分析:

单次修改:\(O(B)\),暴力建树:\(O(N)\);修改次数:\(O(Q)\),建树次数:\(O(\frac{Q}{B})\)。总时间复杂度:

\[O(Q \cdot B + \frac{Q}{B}N) \\ \]

\(B=\sqrt{N}\) 时,取最小值:

\[O(Q\sqrt{N}) \\ \]

法 2:长链剖分树上 \(K\) 级祖先 + 二分 + 序列分块

考虑在删除一个节点后,会受到影响的只有从根到它的这一条链,而如果要这一条链上的某个点 \(u\) 要从轻节点变为重节点,那么必须满足 \(siz_u \leq \frac{siz_{fa_u}}{2}\),那么反过来,假设我们现在在根节点 \(rt\),那么这一条链上有可能从轻节点变为重节点的子孙节点 \(u\) 必须满足 \(siz_u \leq \frac{siz_{rt}}{2}\),那么我们可以找到最近的那个子孙节点,然后判断变化并更新后将它作为新的根 \(rt'\),继续循环下去,这样就可以保证所有都点被更新到。可以证明,这种操作次数不会大于 \(\log_2{N}\),因为每一次换根,子树大小都要除以 2。

现在考虑怎么找子孙节点,一个显然的思路是二分,毕竟 \(\{siz\}\) 从底部向上肯定是递增的,而 \(siz_{rt}\) 不变。那么我们只需要动态维护 \(\{siz\}\) ,再套用长链剖分求树上 \(K\) 级祖先的方法辅助二分即可,此处用序列分块维护前缀和可以保证复杂度的正确性。

时间复杂度:

  1. 长链剖分树上 \(K\) 级祖先 :
    • 预处理:\(O(N\log_2{N})\)
    • 单次查询:\(O(1)\)
  2. 二分:
    • 单次二分:\(O(\log_2{N})\)
    • 最多重复次数:\(O(\log_2{N})\)
    • 内部操作:\(O(1)\)
  3. 序列分块:
    • 单次修改:\(O(\sqrt{N})\)
    • 单次查询:\(O({1})\)

总时间复杂度:

\[O(N \log_2{N} + Q (\sqrt{N} + \log_2^2{N})) \\ \]

法 3:逆向思维 + 重链剖分

删除节点不好维护,那我们就离线反过来加入节点,并用线段树或树状数组动态维护重链剖分。

加点的时候,只要判断从根到该点这一条链上的轻边是否会变为重边,而动态维护的重链剖分使得这一条链一定只有 \(O(\log_2{N})\) 级别的链,也就是只有 \(O(\log_2{N})\) 级别的轻边,那我们在往上跳的时候只要二分或树状数组倍增查找上一条轻边所在位置即可。

但这里还有一个小细节:当子树的大小一致时,我们需要特判两棵子树中下一个加入的是哪一个,或者在不再加节点的时候判断哪个是左子节点。

时间复杂度:\(O(N \log_2^2{N})\)


CODE

法 1:分块思想

时间复杂度:\(O(Q\sqrt{N})\),空间复杂度:\(O(N)\)

#include<bits/stdc++.h>
#define ll long long
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
constexpr int N=2e5+10;
int n,B,Q;
namespace Tree {
#define ls(p) (tr[p][0])
#define rs(p) (tr[p][1])
#define fa(p) (tr[p].fa)
	ll sum;
	struct node {
		bool col;
		int d,fa,pa,siz,son;
		int sons[2];
		int &operator[](bool ty) {
			return sons[ty];
		}
	} tr[N];
	void Build() {
		FOR(u,1,n)cin>>ls(u)>>rs(u),tr[ls(u)].fa=u,tr[rs(u)].fa=u;
		DOR(u,n,1)
		tr[u].siz=tr[ls(u)].siz+tr[rs(u)].siz+1,tr[u].son=(tr[ls(u)].siz>=tr[rs(u)].siz?ls(u):rs(u));
		FOR(u,1,n)sum+=tr[u].son;
	}
	void Rebuild() {
		DOR(u,n,1)tr[u].siz=1+tr[ls(u)].siz+tr[rs(u)].siz,tr[u].d=tr[ls(u)].siz-tr[rs(u)].siz;
		FOR(u,1,n) {
			tr[ls(u)].pa=tr[rs(u)].pa=abs(tr[u].d)<=B+1?u:tr[u].pa;
			abs(tr[u].d)<=B+1?(tr[ls(u)].col=0,tr[rs(u)].col=1):(tr[ls(u)].col=tr[rs(u)].col=tr[u].col);
		}
	}
	void Del(int u) {
		sum-=tr[u].son,tr[fa(u)][u==rs(fa(u))]=0;
		for(int pa=tr[u].pa; pa; pa=tr[u=pa].pa) {
			tr[pa].d+=(tr[u].col?1:-1),sum-=tr[pa].son;
			if(!tr[pa].d&&!ls(pa)&&!rs(pa))tr[pa].son=0;
			if(tr[pa].d>0)tr[pa].son=ls(pa);
			if(tr[pa].d<0)tr[pa].son=rs(pa);
			sum+=tr[pa].son;
		}
	}
} using namespace Tree;
signed main() {
	cin>>n,B=ceil(sqrt(n)),Build(),cin>>Q,cout<<sum<<endl;
	FOR(t,1,Q) {
		if(t%B==1)Rebuild();
		int u;
		cin>>u,Del(u),cout<<sum<<endl;
	}
	return 0;
}

法 2:长链剖分树上 \(K\) 级祖先 + 二分 + 序列分块

时间复杂度:\(O(N \log_2{N} + Q (\sqrt{N} + \log_2^2{N}))\),空间复杂度:\(O(N\log_2{N})\)

#include<bits/stdc++.h>
#define ll long long
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
constexpr int N=2e5+10,sN=450,lN=18,lV=lN+1;
int Q;
namespace LDT {
	int n,idx;
	int ls[N],rs[N],dl[N],dr[N],Fa[N],Lg[N],dep[N],dfn[N],len[N],siz[N],son[N],Son[N],top[N];
	int fa[N][lV];
	ll sum;
	struct DB {
		int Bl,Bn;
		int st[sN],en[sN],Bsum[sN];
		int id[N],sum[N];
		void init() {
			Bl=ceil(sqrt(n)),Bn=(n-1)/Bl+1;
			FOR(i,1,Bn) {
				st[i]=en[i-1]+1,Bsum[i]=en[i]=min(st[i]+Bl-1,n);
				FOR(j,st[i],en[i])sum[j]=j-st[id[j]=i]+1;
			}
		}
		void Plus(int x,int d) {
			FOR(i,x,en[id[x]])sum[i]+=d;
			FOR(i,id[x],Bn)Bsum[i]+=d;
		}
		int Sum(int x) {
			return Bsum[id[x]-1]+sum[x];
		}
		int Sum(int l,int r) {
			return Sum(r)-Sum(l-1);
		}
	} db;
	void dfs0(int u) {
		dep[u]=dep[fa[u][0]]+1;
		FOR(i,1,Lg[dep[u]])fa[u][i]=fa[fa[u][i-1]][i-1];
		if(ls[u])fa[ls[u]][0]=u,dfs0(ls[u]);
		if(rs[u])fa[rs[u]][0]=u,dfs0(rs[u]);
		len[u]=max(len[ls[u]],len[rs[u]])+1,Son[u]=(len[ls[u]]>=len[rs[u]]?ls[u]:rs[u]);
		siz[u]=siz[ls[u]]+siz[rs[u]]+1,son[u]=(siz[ls[u]]>=siz[rs[u]]?ls[u]:rs[u]);
	}
	void dfs1(int u,int Top,int Pa) {
		top[dfn[dl[u]=++idx]=u]=Top,Fa[idx]=Pa;
		if(Son[u])dfs1(Son[u],Top,fa[Pa][0]);
		if(ls[u]&&ls[u]!=Son[u])dfs1(ls[u],ls[u],ls[u]);
		if(rs[u]&&rs[u]!=Son[u])dfs1(rs[u],rs[u],rs[u]);
		dr[u]=idx;
	}
	int Bro(int u) {
		return ls[fa[u][0]]==u?rs[fa[u][0]]:ls[fa[u][0]];
	}
	int Pa(int u,int k) {
		return k?(u=fa[u][Lg[k]],(k-=(1<<Lg[k])+dep[u]-dep[top[u]])>0?Fa[dl[top[u]]+k]:dfn[dl[top[u]]-k]):u;
	}
	int Siz(int u) {
		return u?db.Sum(dl[u],dr[u]):0;
	}
	void Build() {
		cin>>n,Lg[0]=-1;
		FOR(u,1,n)cin>>ls[u]>>rs[u],Lg[u]=Lg[u>>1]+1;
		dfs0(1),dfs1(1,1,1),sum=accumulate(son+1,son+n+1,0ll);
	}
} using namespace LDT;
signed main() {
	for(Build(),db.init(),cin>>Q,cout<<sum<<endl; Q; --Q) {
		int u,rt=1;
		cin>>u,db.Plus(dl[u],-1);
		for(int ans=0,x=0,y=0; true; ans=0,rt=x,x=0,y=0) {
			for(int l=dep[rt]+1,r=dep[u],mid=(l+r)>>1; l<=r; mid=(l+r)>>1)
				Siz(Pa(u,dep[u]-mid))<=(Siz(rt)>>1)?ans=mid,r=mid-1:l=mid+1;
			if(!ans)break;
			y=Bro(x=Pa(u,dep[u]-ans));
			if(son[fa[x][0]]==x) {
				if(Siz(x)+1==Siz(y))sum+=y-x,son[fa[x][0]]=y;
				else if(!Siz(x)&&!Siz(y))sum-=x,son[fa[x][0]]=0;
			}
		}
		cout<<sum<<endl;
	}
	return 0;
}

法 3:逆向思维 + 重链剖分

时间复杂度:\(O(N \log_2^2{N})\),空间复杂度:\(O(N)\)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define tomax(a,b) ((a)=max((a),(b)))
#define tomin(a,b) ((a)=min((a),(b)))
#define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d))
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define EDGE(g,i,u,x) for(register int i=(g).h[(u)],x=(g).v[(i)];(i);(i)=(g).nxt[(i)],(x)=(g).v[(i)])
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
constexpr int N=2e5+10,lN=18,lV=lN+1;
int Q;
int qr[N];
ll ans[N];
namespace WDT {
	int n,idx;
	int a[N],dl[N],dr[N],fa[N],ls[N],rs[N],dep[N],dfn[N],son[N],top[N];
	ll sum;
	struct BIT {
#define lowbit(i) ((i)&-(i))
		int c[N];
		int &operator[](int i) {
			return c[i];
		}
		void Init() {
			FOR(i,1,n)c[i]=1;
		}
		void Build() {
			FOR(i,1,n)if(i+lowbit(i)<=n)c[i+lowbit(i)]+=c[i];
		}
		void Plus(int x,int d) {
			if(x>0)for(int i=x; i<=n; i+=lowbit(i))c[i]+=d;
		}
		int Sum(int x) {
			int ans=0;
			if(x>0)for(int i=x; i; i^=lowbit(i))ans+=c[i];
			return ans;
		}
		int Sum(int l,int r){
			return Sum(r)-Sum(l-1);
		}
		int Bound(int k) {
			int ans=0,sum=0;
			DOR(i,lN,0)if(ans+(1<<i)<=n&&sum+c[ans+(1<<i)]<k)sum+=c[ans+=1<<i];
			return ans+1;
		}
#undef lowbit
	} light,siz;
	struct SEG {
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((tr[p].l+tr[p].r)>>1)
		struct node {
			int l,r,mi;
		} tr[N<<2];
		void Up(int p) {
			tr[p].mi=min(tr[ls].mi,tr[rs].mi);
		}
		void Build(int p,int l,int r) {
			tr[p]= {l,r,0};
			if(l==r)return tr[p].mi=a[dfn[l]],void();
			Build(ls,l,mid),Build(rs,mid+1,r),Up(p);
		}
		void Change(int p,int x,int d) {
			return tr[p].l==tr[p].r?tr[p].mi=d,void():(Change(x<=mid?ls:rs,x,d),Up(p));
		}
		int Min(int p,int l,int r) {
			if(l<=tr[p].l&&tr[p].r<=r)return tr[p].mi;
			return min(l<=mid?Min(ls,l,r):INF,mid<r?Min(rs,l,r):INF);
		}
#undef ls
#undef rs
#undef mid
	} seg;
	int dfs0(int u) {
		int lsiz=0,rsiz=0;
		a[u]=INF,dep[u]=dep[fa[u]]+1,son[u]=0;
		if(ls[u])fa[ls[u]]=u,lsiz=dfs0(ls[u]);
		if(rs[u])fa[rs[u]]=u,rsiz=dfs0(rs[u]);
		return son[u]=lsiz>=rsiz?ls[u]:rs[u],lsiz+rsiz+1;
	}
	void dfs1(int u,int Top) {
		top[dfn[dl[u]=++idx]=u]=Top;
		if(son[u])dfs1(son[u],Top);
		if(ls[u]&&ls[u]!=son[u])dfs1(ls[u],ls[u]);
		if(rs[u]&&rs[u]!=son[u])dfs1(rs[u],rs[u]);
		dr[u]=idx;
	}
	int Siz(int u){
		return siz.Sum(dl[u],dr[u]);
	}
	int Pa(int u){
		for(u=fa[u];u;u=fa[top[u]]){
			int v=dfn[light.Bound(light.Sum(dl[u]))];
			if(dl[top[u]]<=dl[v]&&dr[v]<=dr[top[u]])return v;
		}return 0;
	}
	int Son(int u){
		if(!ls[u])return rs[u];
		if(!rs[u])return ls[u];
		int lsiz=Siz(ls[u]),rsiz=Siz(rs[u]);
		if(lsiz^rsiz)return lsiz>rsiz?ls[u]:rs[u];
		return seg.Min(1,dl[ls[u]],dr[ls[u]])<=seg.Min(1,dl[rs[u]],dr[rs[u]])?ls[u]:rs[u];
	}
	int Bro(int u){
		return ls[fa[u]]==u?rs[fa[u]]:ls[fa[u]];
	}
	void Build() {
		cin>>n,siz.Init();
		FOR(u,1,n)cin>>ls[u]>>rs[u];
		cin>>Q;
		dfs0(1),dfs1(1,1);
		DOR(i,Q,1)cin>>qr[i],a[qr[i]]=i,siz[dl[qr[i]]]=0;
		siz.Build(),seg.Build(1,1,n);
		FOR(i,1,n){
			int son=Son(i);
			if(son&&Siz(son)){
				sum+=son;
				int bro=Bro(son);
				if(bro&&Siz(bro))light[dl[bro]]=1;
			}
		}
		light.Build();
	}
	void Insert(int u){
		seg.Change(1,dl[u],INF),siz.Plus(dl[u],1);
		if(Son(fa[u])==u){
			if(Siz(Bro(u)))sum-=Bro(u),light.Plus(dl[Bro(u)],1);
			sum+=u;
		}else light.Plus(dl[u],1);
		for(int pa=fa[u=Pa(u)];u&&fa[u];pa=fa[u=Pa(u)])if(Son(pa)==u){
			if(Siz(Bro(u)))sum-=Bro(u),light.Plus(dl[Bro(u)],1);
			sum+=u,light.Plus(dl[u],-1);
		}
	}
} using namespace WDT;
signed main() {
	Build();
	FOR(i,1,Q)ans[i]=sum,Insert(qr[i]);
	ans[Q+1]=sum,reverse(ans+1,ans+Q+2);
	FOR(i,1,Q+1)cout<<ans[i]<<endl;
	return 0;
}

posted @ 2024-07-29 16:02  Add_Catalyst  阅读(39)  评论(0)    收藏  举报