ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

学习笔记——树链剖分

2022-01-26 14:34:10  阅读:169  来源: 互联网

标签:剖分 siz ll 笔记 树链 dfn maxn inline include


思想

对于每个节点,把所有子节点中子树最大的一个,成为重点,其它成为轻点。重点到父亲节点的连线成为重边,重边连接成若干条重链,其余的每个点称为重链。

可以发现,如果路径经过一条轻边,那么现在的子树大小至少缩小一半,所以每条路径可以被拆分成最多 \(\log n\) 条链,这样一来,就可以把较难维护的树形结构,改变成若干条链,并且可以发现按照先重后轻的原则遍历时,属于相同重链或相同子树的遍历序是连续的,就可以根据这个性质用线段树维护。

每个链在线段树上的区间是 \([dfn(top(u)),dfn(u)]\),每个子树在线段树上的区间是 \([dfn(u),dfn(u)+siz(u)-1]\),根据这个进行线段树操作。

模板

P3384 轻重链剖分/树链剖分

Code

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef int ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e6+10;
const int maxm=1e6+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7ffffff;
const ll minxn=-0x7ffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
    ll x=0,w=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*w;
}
inline void write(ll x){
    if(x<0){putchar('-');write(-x);return;}
    if(x>=10) write(x/10);
    putchar(x%10+'0');
}
ll n,m,root,p;
struct edge{
    ll to,nxt;
}e[maxn];
ll cnt,head[maxn];
ll w[maxn],dfnw[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt;
inline void add_edge(ll u,ll v){
    e[++cnt].to=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
//第一次dfs遍历深度,父亲,子树大小和重儿子
inline void dfs1(ll u,ll f,ll d){
    dep[u]=d,fa[u]=f,siz[u]=1;
    ll maxson=-1;
    for(int i=head[u];i;i=e[i].nxt){
        ll v=e[i].to;
        if(v==f) continue;
        dfs1(v,u,d+1);
        siz[u]+=siz[v];
        if(siz[v]>maxson){
            son[u]=v;
            maxson=siz[v];
        }
    }
}
//第二次dfs遍历重链的顶点和dfs序
inline void dfs2(ll u,ll t){
    dfn[u]=++dfncnt,dfnw[dfncnt]=w[u],top[u]=t;
    if(!son[u]) return;
    dfs2(son[u],t);//先遍历重儿子保证dfs序连续
    for(int i=head[u];i;i=e[i].nxt){
        ll v=e[i].to;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);//轻儿子是所在重链的顶点
    }
}
ll num[maxn],laz[maxn];
//线段树维护
struct XDS{
    inline void build(ll rt,ll l,ll r){
        if(l==r){
            num[rt]=dfnw[l]%p;
            return;
        }
        build(lson),build(rson);
        num[rt]=(num[rt<<1]+num[rt<<1|1])%p;
    }
    inline void push_down(ll rt,ll l,ll r){
        laz[rt<<1]+=laz[rt],laz[rt<<1|1]+=laz[rt];
        num[rt<<1]+=laz[rt]*(mid-l+1),num[rt<<1|1]+=laz[rt]*(r-mid);
        num[rt<<1]%=p,num[rt<<1|1]%=p,laz[rt]=0;
    }
    inline void update(ll rt,ll l,ll r,ll pl,ll pr,ll k){
        if(pl<=l&&r<=pr){
            laz[rt]+=k;
            num[rt]+=k*len;
        }
        else{
            if(laz[rt]) push_down(rt,l,r);
            if(pl<=mid) update(lson,pl,pr,k);
            if(pr>mid) update(rson,pl,pr,k);
            num[rt]=(num[rt<<1]+num[rt<<1|1])%p;
        }
    }
    inline ll query(ll rt,ll l,ll r,ll pl,ll pr){
        if(pl<=l&&r<=pr){
            return num[rt]%p;
        }
        else{
            if(laz[rt]) push_down(rt,l,r);
            ll sum=0;
            if(pl<=mid) sum+=query(lson,pl,pr);
            if(pr>mid) sum+=query(rson,pl,pr);
            return sum;
        }
    }
}tree;
//处理区间时,因为一定在一条重链向下延伸的重链中,所以一直向上跳深度较大的一点直到lca
inline void update_pt(ll x,ll y,ll k){
    k%=p;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        tree.update(1,1,n,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    tree.update(1,1,n,dfn[x],dfn[y],k);
}
inline ll query_pt(ll x,ll y){
    ll sum=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        sum=(sum+tree.query(1,1,n,dfn[top[x]],dfn[x]))%p;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    sum=(sum+tree.query(1,1,n,dfn[x],dfn[y]))%p;
    return sum%p;
}
//处理子树时,因为dfs序一定是连续的,所以与线段树区间处理相同
inline void update_tr(ll x,ll k){
    tree.update(1,1,n,dfn[x],dfn[x]+siz[x]-1,k);
}
inline ll query_tr(ll x){
    return tree.query(1,1,n,dfn[x],dfn[x]+siz[x]-1)%p;
}
int main(){
    n=read(),m=read(),root=read(),p=read();
    for(int i=1;i<=n;i++){
        w[i]=read();
    }
    for(int i=1;i<n;i++){
        ll u=read(),v=read();
        add_edge(u,v),add_edge(v,u);
    }
    dfs1(root,0,1);
    dfs2(root,root);
    tree.build(1,1,n);
    while(m--){
        ll opt=read();
        if(opt==1){
            ll x=read(),y=read(),k=read();
            update_pt(x,y,k);
        }
        else if(opt==2){
            ll x=read(),y=read();
            printf("%d\n",query_pt(x,y)%p);
        }
        else if(opt==3){
            ll x=read(),k=read();
            update_tr(x,k);
        }
        else{
            ll x=read();
            printf("%d\n",query_tr(x)%p);
        }
    }
}

例题

1.P2690 ZJOI2008 书的统计

甚至比模板还要简单,没有复杂的转换,只需要维护区间和以及区间最大值,直接树剖套线段树。

代码鸽了。

2.P2486 SDOI2011 染色

一道很有趣的题。

因为要维护连续颜色段数,线段树的查询和修改就略有不同,函数参数应为:当前下标,当前区间,当前查询区间,要求查询区间,这样能够维护出边界的颜色,来特判左右两段是否合并为一个。

ACcode

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef int ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e6+10;
const int maxm=1e6+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7ffffff;
const ll minxn=-0x7ffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
    ll x=0,w=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*w;
}
inline void write(ll x){
    if(x<0){putchar('-');write(-x);return;}
    if(x>=10) write(x/10);
    putchar(x%10+'0');
}
ll n,m;
struct edge{
    ll to,nxt;
}e[maxn];
ll cnt,head[maxn];
inline void add_edge(ll u,ll v){
    e[++cnt].to=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
ll w[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt;
inline void dfs1(ll u,ll f,ll d){
    dep[u]=d,fa[u]=f,siz[u]=1;
    ll maxson=-1;
    for(int i=head[u];i;i=e[i].nxt){
        ll v=e[i].to;
        if(v==f) continue;
        dfs1(v,u,d+1);
        siz[u]+=siz[v];
        if(siz[v]>maxson){
            son[u]=v;
            maxson=siz[v];
        }
    }
}
inline void dfs2(ll u,ll t){
    dfn[u]=++dfncnt,top[u]=t;
    if(!son[u]) return;
    dfs2(son[u],t);
    for(int i=head[u];i;i=e[i].nxt){
        ll v=e[i].to;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}
ll num[maxn],laz[maxn],lc[maxn],rc[maxn];
ll cl,cr;
struct XDS{
	inline void push_down(ll rt){
		if(laz[rt]){
			laz[rt<<1]=laz[rt<<1|1]=laz[rt];
			num[rt<<1]=num[rt<<1|1]=1;
			lc[rt<<1]=rc[rt<<1]=lc[rt];
			lc[rt<<1|1]=rc[rt<<1|1]=lc[rt];
			laz[rt]=0;
		}
	}
	inline void push_up(ll rt){
		lc[rt]=lc[rt<<1],rc[rt]=rc[rt<<1|1];
		ll ans=num[rt<<1]+num[rt<<1|1];
		if(rc[rt<<1]==lc[rt<<1|1]) ans--;
		num[rt]=ans;
	}
	inline void build(ll rt,ll l,ll r){
		num[rt]=0;
		if(l==r){
			return;
		}
		build(lson),build(rson);
	}
	inline void update(ll rt,ll l,ll r,ll nl,ll nr,ll k){
		if(nl==l&&r==nr){
			num[rt]=laz[rt]=1;
			lc[rt]=rc[rt]=k;
			return;
		}
		push_down(rt);
		if(nr<=mid) update(lson,nl,nr,k);
		else if(nl>mid) update(rson,nl,nr,k);
		else{
			update(lson,nl,mid,k),update(rson,mid+1,nr,k);
		}
		push_up(rt);
	}
	inline ll query(ll rt,ll l,ll r,ll nl,ll nr,ll pl,ll pr){
		if(l==pl) cl=lc[rt];
		if(r==pr) cr=rc[rt];
		if(nl==l&&r==nr){
			return num[rt];
		}
		push_down(rt);
		if(pr<=mid) return query(lson,nl,nr,pl,pr);
		else if(pl>mid) return query(rson,nl,nr,pl,pr);
		else{
			ll ans=query(lson,nl,mid,pl,pr)+query(rson,mid+1,nr,pl,pr);
			if(rc[rt<<1]==lc[rt<<1|1]) ans--;
			return ans;
		}
		push_up(rt);
	}
}tree;
inline void update_pt(ll u,ll v,ll k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		tree.update(1,1,n,dfn[top[u]],dfn[u],k);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	tree.update(1,1,n,dfn[u],dfn[v],k);
}
inline ll query_pt(ll u,ll v){
	ll ansl=-1,ansr=-1;
	ll ans=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v),swap(ansl,ansr);
		ans+=tree.query(1,1,n,dfn[top[u]],dfn[u],dfn[top[u]],dfn[u]);
		if(cr==ansl) ans--;
		ansl=cl;
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v),swap(ansl,ansr);
	ans+=tree.query(1,1,n,dfn[v],dfn[u],dfn[v],dfn[u]);
	if(cr==ansl) ans--;
	if(cl==ansr) ans--;
	return ans;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		w[i]=read();
	}
	for(int i=1;i<n;i++){
		ll u=read(),v=read();
		add_edge(u,v),add_edge(v,u);
	}
	dfs1(1,0,1);
	dfs2(1,1);
	tree.build(1,1,n);
	for(int i=1;i<=n;i++){
		tree.update(1,1,n,dfn[i],dfn[i],w[i]);
	}
	while(m--){
		char opt;
		cin>>opt;
		if(opt=='C'){
			ll u=read(),v=read(),k=read();
			update_pt(u,v,k);
		}
		else{
			ll u=read(),v=read();
			printf("%d\n",query_pt(u,v));
		}
	}
}

3.P2590 ZJOI2008 树的统计

模板题,代码不贴了。

4.P1967 货车运输

\(\operatorname{Kruskal}\)最大生成树重构一下,可以放到树剖里跑,因为维护的是树上边权最小值,可以把边权挂在子节点上,处理在同一重链的 \([L,R]\) 的路径时,线段树查询 \([L+1,R]\) 的最小值即可。

两个\(\operatorname{dfs}\)

ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],tow[maxn],dfnw[maxn],dfncnt;
inline void dfs1(ll u,ll f,ll d){
	dep[u]=d,fa[u]=f,siz[u]=1;
	ll maxson=-1;
	for(int i=head[u];i;i=e[i].nxt){
		ll v=e[i].to;
		if(v==f) continue;
		tow[v]=e[i].w;
		dfs1(v,u,d+1);
		siz[u]+=siz[v];
		if(siz[v]>maxson){
			son[u]=v;
			maxson=siz[v];
		}
	}
}
inline void dfs2(ll u,ll t){
	dfn[u]=++dfncnt,dfnw[dfncnt]=tow[u],top[u]=t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];i;i=e[i].nxt){
		ll v=e[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}

查询操作

inline ll query_ptmin(ll u,ll v){
	ll ans=maxxn;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		ans=min(ans,tree.query_min(1,1,n,dfn[top[u]],dfn[u]));
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	ans=min(ans,tree.query_min(1,1,n,dfn[v]+1,dfn[u]));
	return ans;
}

5.P3979 遥远的国度

换根的树剖,支持区间修改和区间维护最小值。

对于换根,因为树的性质不变,所以如果在初始化以 \(1\) 为根的树中,现在根节点和查询节点同属一颗子树的同左或同右的子树,就直接查询这个子树,如果在异侧那就找到子树的根,然后查询两次。

ACcode

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e5+10;
const int maxm=4e5+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7fffffff;
const ll minxn=-0x7fffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
    ll x=0,w=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*w;
}
inline void write(ll x){
    if(x<0){putchar('-');write(-x);return;}
    if(x>=10) write(x/10);
    putchar(x%10+'0');
}
ll n,m,root;
struct edge{
	ll to,nxt;
}e[maxm];
ll head[maxn],cnt;
inline void add_edge(ll u,ll v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
ll w[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt,dfnw[maxn];
inline void dfs1(ll u,ll f,ll d){
	fa[u]=f,dep[u]=d,siz[u]=1;
	ll maxson=-1;
	for(int i=head[u];i;i=e[i].nxt){
		ll v=e[i].to;
		if(v==f) continue;
		dfs1(v,u,d+1);
		siz[u]+=siz[v];
		if(siz[v]>maxson){
			maxson=siz[v];
			son[u]=v;
		}
	}
}
inline void dfs2(ll u,ll t){
	dfn[u]=++dfncnt,dfnw[dfncnt]=w[u],top[u]=t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];i;i=e[i].nxt){
		ll v=e[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}	
}
ll minx[maxm],laz[maxm];
struct XDS{
	inline void push_up(ll rt){
		minx[rt]=min(minx[rt<<1],minx[rt<<1|1]);
	}
	inline void build(ll rt,ll l,ll r){
		if(l==r){
			minx[rt]=dfnw[l];
			return;
		}
		build(lson),build(rson);
		push_up(rt);
	}
	inline void push_down(ll rt){
		if(laz[rt]){
			laz[rt<<1]=laz[rt<<1|1]=laz[rt];
			minx[rt<<1]=minx[rt<<1|1]=laz[rt];
			laz[rt]=0;
		}
	}
	inline void updatex(ll rt,ll l,ll r,ll ql,ll qr,ll k){
		if(ql<=l&&r<=qr){
			minx[rt]=laz[rt]=k;
			return;
		}
		push_down(rt);
		if(ql<=mid) updatex(lson,ql,qr,k);
		if(qr>mid) updatex(rson,ql,qr,k);
		push_up(rt);
	}
	inline ll queryx(ll rt,ll l,ll r,ll ql,ll qr){
		if(ql<=l&&r<=qr){
			return minx[rt];
		}
		ll ans=maxxn;
		push_down(rt);
		if(ql<=mid) ans=min(ans,queryx(lson,ql,qr));
		if(qr>mid) ans=min(ans,queryx(rson,ql,qr));
		push_up(rt);
		return ans;
	}
}tree;
inline void update_pt(ll u,ll v,ll k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		tree.updatex(1,1,n,dfn[top[u]],dfn[u],k);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	tree.updatex(1,1,n,dfn[u],dfn[v],k);
}
inline ll find(ll u){
	if(u==root) return -1;
	if(dfn[u]>=dfn[root]||dfn[u]+siz[u]-1<dfn[root]) return 0;
	ll tmp=root;
	while(top[tmp]!=top[u]){
		if(fa[top[tmp]]==u) return top[tmp];
		tmp=fa[top[tmp]];
	}
	return son[u];
}
inline ll query_tr(ll u){
	ll ty=find(u);
	if(ty==-1) return minx[1];
	if(!ty) return tree.queryx(1,1,n,dfn[u],dfn[u]+siz[u]-1);
	else{
		ll ans=tree.queryx(1,1,n,1,dfn[ty]-1);
		if(dfn[ty]+siz[ty]-1!=n){
			ans=min(ans,tree.queryx(1,1,n,dfn[ty]+siz[ty],n));
		}
		return ans;
	}
}
int main(){
	n=read(),m=read();
	for(int i=1;i<n;i++){
		ll u=read(),v=read();
		add_edge(u,v),add_edge(v,u);
	}
	for(int i=1;i<=n;i++){
		w[i]=read();
	}
	dfs1(1,0,1);
	dfs2(1,1);
	tree.build(1,1,n);
	root=read();
	for(int i=1;i<=m;i++){
		ll opt=read();
		if(opt==1) root=read();
		else if(opt==2){
			ll u=read(),v=read(),k=read();
			update_pt(u,v,k);
		}
		else{
			ll u=read();
			printf("%lld\n",query_tr(u));
		}
	}
	return 0;
}

5.P1505 国家集训队 旅游

多用懒标记维护几个东西就ok了,因为要维护最大值以及最小值,所以取负的时候直接交换最大最小值。

放个上传和下传代码

inline void push_up(ll rt){
	maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
	minx[rt]=min(minx[rt<<1],minx[rt<<1|1]);
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
inline void push_down(ll rt){
	laz[rt<<1]^=1,laz[rt<<1|1]^=1;
	maxx[rt<<1]=-maxx[rt<<1],maxx[rt<<1|1]=-maxx[rt<<1|1];
	minx[rt<<1]=-minx[rt<<1],minx[rt<<1|1]=-minx[rt<<1|1];
	sum[rt<<1]=-sum[rt<<1],sum[rt<<1|1]=-sum[rt<<1|1];
	swap(maxx[rt<<1],minx[rt<<1]);
	swap(maxx[rt<<1|1],minx[rt<<1|1]);
	laz[rt]=0;
}

6.P3258 JLOI2014 松鼠的新家

区间修改+单点查询

注意每次走的终点要减\(1\),因为会重复。

7.P2146 NOI2015 软件包管理器

  • 安装操作:把根节点到当前节点路径赋值为\(1\)
  • 卸载操作:当前节点子树赋值为\(0\)

输出变化量。

标签:剖分,siz,ll,笔记,树链,dfn,maxn,inline,include
来源: https://www.cnblogs.com/SoyTony/p/15846386.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有