zoukankan      html  css  js  c++  java
  • 树上求值总结

    树上求值无非就这几种常见类型:

    一 求子树的值

    方法1:DFS做差

    条件:可离线+可前缀做差

    复杂度:(O(n))

    优点:时间短

    大致流程:

    void DFS(int x,int fa){
    	update_ans(x);//拿遍历子树前的值更新答案
    	for(int i=0;i<G[x].size();i++){//遍历子树 
    		int t=G[x][i];
    		if(t==fa)continue;
    		DFS(t,x);
    	}
    	update_ans(x);//拿遍历子树后的值更新答案
    }
    

    方法2:Dsu on tree

    条件:可离线

    复杂度:(O(nlog(n)))

    优点:十分暴力,可以维护不具有单调性的值

    要点:维护的容器需要开全局,重儿子要放到最后遍历并且保证其只会被加入一次
    大致流程:
    例题:[CF]Tree and Queries

    void DFS(int x,int fa){//预处理每个节点的重儿子 
    	size[x]=1;
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i];
    		if(t==fa)continue;
    		DFS(t,x);
    		if(size[t]>size[son[x]])son[x]=t;//更新重儿子 
    		size[x]+=size[t];
    	}
    }
    void solve(int x,int fa){
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i];
    		if(t==fa||t==son[x])continue;//先遍历轻儿子
    		solve(t,x);
    		Erase(t);//暴力把轻儿子的值删掉,以防止遍历其兄弟时造成影响 
    	}
    	if(son[x])solve(son[x],x);//遍历重儿子,这样可以保证回溯时不用将重儿子的值删去
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i];
    		if(t==fa||t==son[x])continue;
    		Add(t);//暴力把轻儿子的值在加进来,以用来查询该节点的值 
    	}
    	Get_Ans(x);//查询这个节点 
    }
    

    方法3:DFS序+线段树/树状数组等区间型数据结构

    条件:维护的值存在某种单调性

    复杂度: (O(nlog(n)))

    优点:可在线操作

    要点:每个节点对应的区间编号不要和原编号弄混
    大致流程:

    void DFS(int x,int fa){//预处理DFS序,把树转成区间,直接维护区间即可 
    	L[x]=++mark;
    	bel[mark]=x;//这个表示DFS序中的编号所对应的的原编号,有时需要用 
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i];
    		if(t==fa)continue;
    		DFS(t,x);
    	}
    	R[x]=mark;
    }
    

    二 求路径的值

    方法1:LCA做差

    条件:可前缀做差

    复杂度:除找LCA外,遍历复杂度是(O(n))

    优点:时间较短,可以搭配上线段树等数据结构

    原理与要点:应用了(ans_{x,y}=ans_{root,x}+ans_{root,y}-2 imes ans_{root,lca_{x,y}})的做差性质(root表示根节点)显然,这样只用维护一个节点到根节点的值即可
    大致流程:

    void DFS(int x,int fa){//预处理,为倍增法求LCA做准备 
    	pre[x][0]=fa;
    	deep[x]=deep[fa]+1;
    	for(int i=1;(1<<i)<=deep[x];i++)pre[x][i]=pre[pre[x][i-1]][i-1];//求x向上走2^i步的祖先 
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i];
    		if(t==fa)continue;
    		DFS(t,x);
    	}
    }
    int LCA(int x,int y){//用倍增法求LCA,其中lg[i]满足2^(lg[i]-1)<=i&&i<2^lg[i] 
    	if(deep[x]<deep[y])swap(x,y);//保证x的深度大于y的深度 
    	while(deep[x]>deep[y])x=pre[x][lg[deep[x]-deep[y]]-1];//跳到同一深度 
    	if(x==y)return x;//相等直接return掉 
    	for(int i=lg[deep[x]]-1;i>=0;i--){//倍增跳跃 
    		if(pre[x][i]==pre[y][i])continue;
    		x=pre[x][i],y=pre[y][i];
    	}
    	return pre[x][0];//在向上走一步 
    }
    void solve(int x,int fa){//再遍历一次,用那个式子求一波答案 
    }
    

    方法2:树剖+线段树/树状数组等区间型数据结构

    条件:维护的值存在某种单调性

    复杂度: 不算跳重链的话,为(O(nlog(n)))

    优点:可以支持路径区间修改操作,功能强大

    要点:码量巨大,注意细节细节细节!!!
    大致流程(以线段树为例):
    例题:[AHOI2005]航线规划

    void DFS_first(int x,int fa){//一遍DFS:预处理每个节点的重儿子,以及其父亲 
    	size[x]=1;
    	pre[x]=fa;
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i];
    		if(t==fa)continue;
    		DFS_first(t,x);
    		if(size[t]>size[son[x]])son[x]=t;//更新重儿子 
    		size[x]+=size[t];
    	}
    }
    void DFS_second(int x,int fa){//二遍DFS:预处理出树剖序、一个节点能到达一个树剖序连续链的最顶端 
    	dfsn[x]=++mark;//树剖序 
    	top[x]=fa;//fa表示能到达的最顶端 
    	if(son[x])DFS_second(son[x],fa);//优先遍历重儿子,使树剖序连续的链尽可能长 
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i];
    		if(t==pre[x]||t==son[x])continue;
    		DFS_second(t,t);//遍历轻儿子,此时dfsn[x]和dfsn[t]不再是连续的,因此要将fa设置成t 
    	}
    }
    void change(int x,int y){//改变x到y的路径上的值 
    	while(top[x]!=top[y]){//这敲法类似树剖找LCA的过程,当top[x]=top[y]时,说明x,y在一条树剖序连续的链上,就可以退出了 
    		if(deep[top[x]]<deep[top[y]])swap(x,y);//确保top[x]的深度大于top[y],不然可能会重复更新 
    		update_sgt(dfsn[top[x]],dfsn[x]);//在线段树上更新区间 
    		x=pre[top[x]];//跳出这条树剖序连续的链 
    	}
    	//最后x,y会在一条树剖序连续的链上,依然还需要进行一次更新 
    	if(deep[x]<deep[y])swap(x,y);
    	if(deep[x]!=deep[y])update_sgt(dfsn[y],dfsn[x]);//如果是更新点的话if(deep[x]!=deep[y])基本上不用打,这得看你维护什么东西 
    }
    int ask(int x,int y){//查询操作与改变操作类似,就不在赘述 
    	int res=0;
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]])swap(x,y);
    		res+=query_sgt(dfsn[top[x]],dfsn[x]);
    		x=pre[top[x]];
    	}
    	if(deep[x]<deep[y])swap(x,y);
    	if(deep[x]!=deep[y])res+=query_sgt(dfsn[y],dfsn[x]);
    	return res;
    }
    

    方法3:点分治(淀粉质

    条件:可离线,无根树

    复杂度:(O(nlog(n)))

    优点:十分暴力,可以维护不具有单调性的值(与Dsu差不多)

    要点:每次需遍历子树的重心,对于当前点只用考虑与当前点的情况即可
    大致流程:

    void Getroot(int x,int fa){//找重心
    	size[x]=1,W[x]=0;//size表示子树大小,W表示儿子中子树大小最大值
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i].ed;
    		if(t==fa||vis[t])continue;//vis表示当前节点是否被遍历到,确保下一个节点没有被遍历到
    		Getroot(t,x);
    		size[x]+=size[t];
    		W[x]=max(W[x],size[t]);
    	}
    	W[x]=max(W[x],SIZE-size[x]);//因为这是无根树,所以其父亲也有可能是它儿子(若以x为根的话)
    	if(W[x]<W[root])root=x;//更新重心,注意root初值应为0,W[0]应为无穷大
    }
    void DFS_Point(int x){//点分治
    	UpdateAns(x);//暴力更新答案
    	vis[x]=true;//标记
    	for(int i=0;i<G[x].size();i++){
    		int t=G[x][i].ed;
    		if(vis[t])continue;
    		Getroot(t,x);//暴力找重心
    		DFS_Point(root);//继续分治
    	}
    }
    
  • 相关阅读:
    【HDU4261】Estimation-DP+优先队列优化
    【POJ3744】Scout YYF I-概率DP+矩阵加速优化
    【POJ3744】Scout YYF I-概率DP+矩阵加速优化
    【HDU2294】Pendant-DP矩阵优化
    【HDU2294】Pendant-DP矩阵优化
    【BZOJ1269】文本编辑器editor(AHOI2006)-NOI原题升级版
    【BZOJ1269】文本编辑器editor(AHOI2006)-NOI原题升级版
    【NOI2003T2】文本编辑器Editor-伸展树数列操作
    zk create() 方法
    FLUSH TABLES WITH READ LOCK 锁全局
  • 原文地址:https://www.cnblogs.com/SillyTieT/p/11264729.html
Copyright © 2011-2022 走看看