树剖 - 其实并没有那么难

树剖

将树剖分为链,然后我们就可以利用我们非常熟悉的线段树来做了

例题:luogu3384
ACcode

#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)

const int maxn=200000+10;
int n,m,r,mod;
int e,beg[maxn],nex[maxn],to[maxn],w[maxn],wt[maxn];
int a[maxn<<2],laz[maxn<<2];
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn]; 
int res=0;

inline void add(int x,int y){
    to[++e]=y;
    nex[e]=beg[x];
    beg[x]=e;
}
inline void pushdown(int rt,int lenn){
    laz[rt<<1]+=laz[rt];
    laz[rt<<1|1]+=laz[rt];
    a[rt<<1]+=laz[rt]*(lenn-(lenn>>1));
    a[rt<<1|1]+=laz[rt]*(lenn>>1);
    a[rt<<1]%=mod;
    a[rt<<1|1]%=mod;
    laz[rt]=0;
}

inline void build(int rt,int l,int r){
    if(l==r){
        a[rt]=wt[l];
        if(a[rt]>mod)a[rt]%=mod;
        return;
    }
    build(lson);
    build(rson);
    a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
}

inline void query(int rt,int l,int r,int L,int R){
    if(L<=l&&r<=R){res+=a[rt];res%=mod;return;}
    else{
        if(laz[rt])pushdown(rt,len);
        if(L<=mid)query(lson,L,R);
        if(R>mid)query(rson,L,R);
    }
}

inline void update(int rt,int l,int r,int L,int R,int k){
    if(L<=l&&r<=R){
        laz[rt]+=k;
        a[rt]+=k*len;
    }
    else{
        if(laz[rt])pushdown(rt,len);
        if(L<=mid)update(lson,L,R,k);
        if(R>mid)update(rson,L,R,k);
        a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
    }
}
inline int qRange(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        res=0;
        query(1,1,n,id[top[x]],id[x]);
        ans+=res;
        ans%=mod;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    res=0;
    query(1,1,n,id[x],id[y]);
    ans+=res;
    return ans%mod;
}

inline void updRange(int x,int y,int k){
    k%=mod;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,id[top[x]],id[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,id[x],id[y],k);
}

inline int qSon(int x){
    res=0;
    query(1,1,n,id[x],id[x]+siz[x]-1);
    return res;
}

inline void updSon(int x,int k){
    update(1,1,n,id[x],id[x]+siz[x]-1,k);
}

inline void dfs1(int x,int f,int deep){ 
    dep[x]=deep;
    fa[x]=f;
    siz[x]=1;
    int maxson=-1;
    for(Rint i=beg[x];i;i=nex[i]){
        int y=to[i];
        if(y==f)continue;
        dfs1(y,x,deep+1);
        siz[x]+=siz[y];
    }
}

inline void dfs2(int x,int topf){//x当前节点,topf当前链的最顶端的节点 
    id[x]=++cnt;//标记每个点的新编号 
    wt[cnt]=w[x];//把每个点的初始值赋到新编号上来 
    top[x]=topf;//这个点所在链的顶端 
    if(!son[x])return;//如果没有儿子则返回 
    dfs2(son[x],topf);//按先处理重儿子,再处理轻儿子的顺序递归处理 
    for(Rint i=beg[x];i;i=nex[i]){
        int y=to[i];
        if(y==fa[x]||y==son[x])continue;
        dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链 
    }
}

int main(){
    read(n);read(m);read(r);read(mod);
    for(Rint i=1;i<=n;i++)read(w[i]);
    for(Rint i=1;i<n;i++){
        int a,b;
        read(a);read(b);
        add(a,b);add(b,a);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,1,n);
    while(m--){
        int k,x,y,z;
        read(k);
        if(k==1){
            read(x);read(y);read(z);
            updRange(x,y,z);
        }
        else if(k==2){
            read(x);read(y);
            printf("%d\n",qRange(x,y));
        }
        else if(k==3){
            read(x);read(y);
            updSon(x,y);
        }
        else{
            read(x);
            printf("%d\n",qSon(x));
        }
    }
}

Tip:可能用到的运算级别 ! > & > |

树剖里的线段树

把树状结构转化为序列问题的想法是不是很妙啊?
gun!
线段树都还没学明白就想刚树剖?滚回去学线段树、、

线段树的节点就按二叉树写啦,不需要结构体

最好写到namespace里面
namespace可好用了:

namespace SGT{
	const int NNN=::NNN<<2;//里面的是NNN,外面的是::NNN,线段树要开4倍 
	ll tree[NNN],lazy[NNN];
	 
	inline void pushup(int p){tree[p]=(tree[p<<1]+tree[p<<1|1])%mod;}
	inline void pushdown(int p,int len/*=r-l+1,注意多个lazy要打*/){
		lazy[p<<1]=lazy[p];
		lazy[p<<1|1]=lazy[p];
		tree[p<<1]+=lazy[p]*(len-(len>>1));
		tree[p<<1|1]+=lazy[p]*(len>>1);
		tree[p<<1]%=mod;
		tree[p<<1|1]%=mod;
	}
	
	inline void build(int p,int l,int r){
		if(l==r){
			tree[p]=wt[l];
			if(wt[l]>mod)tree[p]%=mod;
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
	
	inline void update(int p,int l,int r,int L,int R,int k){
		if(L<=l&&r<=R){
			tree[p]+=k*(r-l+1);
			lazy[p]+=k;
			return ;
		}
		
		int mid=(l+r)>>1;
		if(lazy[p])pushdown(p,r-l+1);
		if(L<=mid)update(p<<1,l,mid,L,R,k);
		if(R>mid)Update(p<<1|1,mid+1,r,L,R,k);
		pushup(p);
	}
	
	inline int query(int p,int l,int r,int L,int R){
		int res=0;
		if(L<=l&&r<=R){
			pushdown(p,r-l+1);
			res+=tree[p];
			res%=mod;
			return res;
		}
		
		int mid=(l+r)>>1;
		if(L<=mid)res+=query(p<<1,l,mid,L,R);
		if(R>mid)res+=query(p<<1|1,mid+1,r,L,R);
		return res;
	}
}

狂打50多行线段树,爽!

轻重链剖分

前置

根据以子节点为根的子树个数size,size最大的为重边,其余为轻边

轻重链有如下性质:(记 u → v u\to v uv e e e,其中 u u u为父节点, v v v为子节点)

  1. 如果 e e e为轻边,那么 s i z e ( v ) < s i z e ( u ) / 2 size(v)<size(u)/2 size(v)<size(u)/2

  2. 从根到某点 t t t如果轻边个数不大于 log ⁡ n \log n logn

    说明:由1.,每经过一条轻边,节点个数减少至少一半,故最多经过 log ⁡ n \log n logn条轻边

  3. 我们称某条路径为重路径(重链),当且仅当它全部由重边组成
    容易发现,那么对于每个点到根的路径上都不超过 log ⁡ n \log n logn条轻边和 log ⁡ n \log n logn条重路径
    一个点在且只在一条重路径上,而每条重路径一定是一条从根结点方向向叶结点方向延伸的深度递增的路径
    你管它怎么发现的,我也不知道

然鹅在算法中这些似乎没有用到

准备工作

算法核心在于计算这几个值:

num[i]:在序列中的i位置的下标
top[i]:i所在重路径的顶部结点
son[i]:i深度最小的重儿子
size[i]:i的子树结点数
dep[i]:i在树中的深度
father[i]:i在树中的父亲

两次DFS实现:

  1. 计算size, dep, father, son
  2. 计算top, num
inline void pre_DFS(int u,int fa,int depth){
	dep[u]=depth;
	father[u]=fa;
	size[u]=1;//一个叶子节点也是一棵子树 
	int heavy_size=-1;
	for(int v:G[u]){
		if(!(v^fa))continue;
		pre_DFS(v,u,depth+1);
		size[u]+=size[v];
		if(size[v]>heavy_size)heavy_size=size[v],son[u]=v;
	}
}

inline void DFS(int u,int t/*top*/){
	num[u]=++cnt;
	top[u]=t;
	wt[cnt]=w[u];
	if(!son[u])return ;
	DFS(son[u],t);
	for(int v:G[u]){
		if(!(v^son[u])||!(v^father[u]))continue;
		DFS(v,v);
	}
}

这两步与建树,都是前置准备工作:

inline void pre_work(){
	pre_DFS(root,0,1);
	DFS(root,root);
    SGT::build(1,1,n);
}

先DFS再build,不然你没有东西build

更新链

利用重链,跳LCA:
每次较深的那个点往上跳,直到两点的top一样
由于top一致的点一定都在一条重路径上,那么深度浅的那个点就是LCA

Tips:
要跳链顶更深的
最后两点没有相遇!所以结束的时候两个点还要在update一次
num[top[u]]<num[u],在写update的时候要小心

inline void chain_updating(int u,int v,int k){
	k%=mod;
	while(top[u]!=top[v]){
		if(dep[num[u]]<dep[num[v]])swap(u,v);
		SGT::update(1,1,n,num[top[u]],num[u],k);
		u=father[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	SGT::update(1,1,n,num[u],num[v],k);
	return ;
}

inline int chain_query(int u,int v){
	int res=0;
	while(top[u]!=top[v]){
		if(dep[num[u]]<dep[num[v]])swap(u,v);
		res+=SGT::query(1,1,n,num[top[u]],num[u]);
		res%=mod;
		u=father[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	res+=SGT::query(1,1,n,num[u],num[v]);
	return res%mod;
}

更新子树

我们在DFS的时候就已经发现:一颗子树就是线段树的一个区间
那么更新子树就只是对这个区间开刀的问题了

这个区间是: [ n u m [ u ] , n u m [ u ] + s i z e [ u ] ) [num[u],num[u]+size[u]) [num[u],num[u]+size[u])

inline void tree_update(int u,int k){
	k%=mod;
	SGT::update(1,1,n,num[u],num[u]+size[u]-1,k);
	return ;
}

inline int tree_query(int u){
	return SGT::query(1,1,n,num[u],num[u]+size[u]-1)%mod;
}

myACcode

#include<bits/stdc++.h>
#define re register
#define in Read()
#define int long long
//#define debug
int in{
	int i=0,f=1;char ch=getchar();
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=2e6+10;
int n,m,root,mod;
int w[NNN],wt[NNN];//w对应树的初始权值,wt对应链的初始权值 

using std::vector;
vector<int>G[NNN];
using std::swap;

int father[NNN],son[NNN],dep[NNN],size[NNN];
int top[NNN],num[NNN],cnt;
int tree[NNN],lazy[NNN];

namespace SGT{
	
	const int NNN=::NNN<<2;//里面的是NNN,外面的是::NNN,线段树要开4倍 
	 
	inline void pushup(int p){tree[p]=(tree[p<<1]+tree[p<<1|1])%mod;}
	inline void pushdown(int p,int len/*=r-l+1,注意多个lazy要打*/){
		lazy[p<<1]+=lazy[p];
		lazy[p<<1|1]+=lazy[p];
		tree[p<<1]+=lazy[p]*(len-(len>>1));
		tree[p<<1|1]+=lazy[p]*(len>>1);
		tree[p<<1]%=mod;
		tree[p<<1|1]%=mod;
		lazy[p]=0;
	}
	
	inline void build(int p,int l,int r){
		if(l==r){
			tree[p]=wt[l];
			if(wt[l]>mod)tree[p]%=mod;
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
	
	inline void update(int p,int l,int r,int L,int R,int k){
		if(L<=l&&r<=R){
			tree[p]+=k*(r-l+1);
			lazy[p]+=k;
			return ;
		}
		
		int mid=(l+r)>>1;
		if(lazy[p]) pushdown(p,r-l+1);
		if(L<=mid)update(p<<1,l,mid,L,R,k);
		if(R>mid)update(p<<1|1,mid+1,r,L,R,k);
		pushup(p);
	}
	
	inline int query(int p,int l,int r,int L,int R){
		int res=0;
		if(L<=l&&r<=R){
			if(lazy[p])pushdown(p,r-l+1);
			res+=tree[p];
			res%=mod;
			return res;
		}
		
		int mid=(l+r)>>1;
		if(lazy[p])pushdown(p,r-l+1);
		if(L<=mid)res+=query(p<<1,l,mid,L,R);
		if(R>mid)res+=query(p<<1|1,mid+1,r,L,R);
		return res;
	}
}

inline void pre_DFS(int u,int fa,int depth){
	dep[u]=depth;
	father[u]=fa;
	size[u]=1;//一个叶子节点也是一棵子树 
	int heavy_size=-1;
	for(int v:G[u]){
		if(!(v^fa))continue;
		pre_DFS(v,u,depth+1);
		size[u]+=size[v];
		if(size[v]>heavy_size)heavy_size=size[v],son[u]=v;
	}
}

inline void DFS(int u,int t/*top*/){
	num[u]=++cnt;
	top[u]=t;
	wt[cnt]=w[u];
	if(!son[u])return ;
	DFS(son[u],t);
	for(int v:G[u]){
		if(!(v^son[u])||!(v^father[u]))continue;
		DFS(v,v);
	}
}

inline void pre_work(){
	pre_DFS(root,0,1);
	DFS(root,root);
	SGT::build(1,1,n);
}

inline void chain_updating(int u,int v,int k){
	k%=mod;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		SGT::update(1,1,n,num[top[u]],num[u],k);
		u=father[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	SGT::update(1,1,n,num[u],num[v],k);
	return ;
}

inline int chain_query(int u,int v){
	int res=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		res+=SGT::query(1,1,n,num[top[u]],num[u]);
		res%=mod;
		u=father[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	res+=SGT::query(1,1,n,num[u],num[v]);
	return res%mod;
}

inline void tree_update(int u,int k){
	k%=mod;
	SGT::update(1,1,n,num[u],num[u]+size[u]-1,k);
	return ;
}

inline int tree_query(int u){
	return SGT::query(1,1,n,num[u],num[u]+size[u]-1)%mod;
}

signed main(){
	n=in,m=in,root=in,mod=in;
	for(re int i=1;i<=n;++i)w[i]=in;
	
	for(re int i=1;i<n;++i){
		int u=in,v=in;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	
	pre_work();
	
	for(re int i=1;i<=m;++i){
		int Q=in;
		switch (Q){
			case 1:{
				int u=in,v=in,k=in;
				chain_updating(u,v,k);
				break;
			}
			case 2:{
				int u=in,v=in,res=0;
				res+=chain_query(u,v);
				printf("%lld\n",res);
				break;
			}
			case 3:{
				int u=in,k=in;
				tree_update(u,k);
				break;
			}
			case 4:{
				int u=in;
				printf("%lld\n",tree_query(u));
				break;
			}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Three.js中的剖切功能可以通过设置剪裁平面对象来实现。通过WebGL渲染器的.clippingPlanes属性设置的剪裁平面对象Plane可以剪裁场景中的所有模型对象。如果你只想剪裁特定的模型对象,可以通过Three.js材质对象的.clippingPlanes属性来实现。要注意的是,一个网格模型所绑定的材质对象的.clippingPlanes属性如果没有设置,那么该模型对象就不会被剪裁。你可以根据需要设置不同模型对象的.clippingPlanes属性来实现剖切效果。\[3\]在实际操作中,你可以导入的obj模型替代原本的网格模型,从而实现剖切及剖切面的绘制。不过这样并没有真正理解模型剖切的原理,如果你想深入学习模型剖切的原理,可以在研究场景渲染效果时加深对着色器的认识,并回过头来分析剖切的原理。\[2\] #### 引用[.reference_title] - *1* *2* [Three.js OBJ模型的剖切](https://blog.csdn.net/weixin_45699870/article/details/108879445)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Three.js剪裁模型](https://blog.csdn.net/u014291990/article/details/102812763)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值