DAY_12(树链剖分)

中途摆烂了几天加上考试比赛啥的,导致目前写博客断了。。差了好几天的题目没学了qwq,现在还是按照每天学的东西来写博客吧

今天主要学了树链剖分,怎么说呢,虽然随便拿出今天写的一道题目来看,码量都是一两百行的,但是其实也不算太恶。。细说:

树链剖分有两种:重链剖分、长链剖分。前者特征是把“最大的”儿子成为重儿子,把树分为若干条重链;后者特征是把“最长链”称为长儿子,把树分为若干条长链。不过在应用中重链应用更多,故树链剖分一般默认为重链剖分。

重链剖分是提高树上搜索效率的一个高级方法。它按照一定规则,把树剖分成一条条线性的不相交的链,因此就将整棵树的操作转换成对链的操作,从而使操作的复杂度转为O(logn),转为从重链部。重链剖分的一个特性:每条链的DFS序是有序的,因此可以使用线段数处理,从而高效的解决一些树上的修改和查询问题。

首先我们来试试水——求lca

现在有两种方法求lca,它的各种算法都是通过快速的向上跳到祖先节点来实现的。

一、倍增法,用二进制递增直接向祖先跳;

二、数链剖分的跳法是:将树剖成从根到叶子的一条条链路,链路之间不相交,每条链上的任意两个相邻节点都是父子关系,每条链路内的节点可以看作一个集合,以链头为集,链路上的节点查询lca时都指向链头,从而实现快速跳。

下面我们来介绍几组概念:

1、size[x]:以x为根子树的大小(对于每个点,x儿子中size最大的作为他的重儿子,剩下的为轻儿子)

2、deep[x]:x的深度;

3、father[x]:x的父亲;

4、top[x]:指x所有在重链顶端;

5、重链:重边连接形成重边连接起来形成的链,每个点恰好属于一条重链;

6、轻边:连接x和x轻儿子的边;

预处理两遍DFS:

第一遍,算出deep[x],size[x],father[x],son[x]

第二遍:算出top[x](重儿子和父节点的链头相同)

lca(Lowest Common Ancestor),所谓LCA,是当给定一个有根树T时,对于任意两个结点u、v,找到一个离根最远的结点x,使得x同时是u和v的祖先,x 便是u、v的最近公共祖先

现在来一道题目:

P3379 【模板】最近公共祖先(LCA)

话不多说,直接上代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,s;
int x,y;
int depth[N];
int sizee[N];
int son[N];
int top[N];
int fath[N];
int head[N];
struct ee
{
	int to;
	int from;
}edge[N<<1];
int cnt;
void add(int u,int v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].from=head[u];
	head[u]=cnt;
}//链式前向星存图 
void dfs1(int u,int fa)
{
	depth[u]=depth[fa]+1;//孩子树高=父亲树高+1 
	fath[u]=fa;//认父 
	sizee[u]=1;//初始化,u节点为本身 
	for(int i=head[u];i;i=edge[i].from)
	{
		int v=edge[i].to;
		if(v!=fa)
		{
			fath[v]=u;
			dfs1(v,u);
			sizee[u]+=sizee[v];
			if(!son[u]||sizee[son[u]]<sizee[v])
			son[u]=v;
		}
	}
}
void dfs2(int u,int topx)
{
	top[u]=topx;
	if(!son[u])
	return ;
	dfs2(son[u],topx);//重链优先 
	for(int i=head[u];i;i=edge[i].from)
	{
		int v=edge[i].to;
		if(v!=fath[u]&&v!=son[u])
		dfs2(v,v);//轻链 
	}
}
int main()
{
	cin>>n>>m>>s;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	dfs1(s,0);
	dfs2(s,s);
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y;
		while(top[x]!=top[y])
		{//x,y不在同一重链 
			if(depth[top[x]]<depth[top[y]])
			swap(x,y);//x成为更浅点 
			x=fath[top[x]];//跳跃 
		}
		if(depth[x]<=depth[y])
		cout<<x<<endl;
		else
		cout<<y<<endl;
	}
	return 0;
}

现在我们来写树链剖分

在数量剖分中,常常有如下几个应用:

1、修改某路径上各点的权值;

2、查询某路径上节点的权值之和;

3、修改以某点为根的子树上各点的权值;

4、查询以某点为根的树上所有节点的权值之和;

那么对于这样的模型,我们就自然而然会想到线段数,由于每条重链内部的节点是有序的,因此可以按DFS序将它们安排在一个线段树上,将每个重链看作一个连续的区间来对重链的内部进行修改和查询。
我们需要进行判断:

如果x和y在一条重链上,那么我们直接将X到Y这一条重链上各点权值累加,并且通过线段数进行修改

如果x和y很不幸不在同一条重点上,而在不同的子树,那么这时候我们需要通过x到lca(x,y)和y到lca(x,y)这两条链上的点的权值进行修改即可。

——但是我们在编程时,我们通常会在查询lca的同时,对所经过的节点直接进行修改——

查询x到y的路径上所有节点权值之和:同样的通过线段数进行查询,然后并进行相关操作即可。

对于以x为根节点的子树上各点权值的修改并查询:由于每棵子树上的所有节点的DFS序是连续的,也就是说每棵子树对应一个连续的区间,那么修改和查询子树与线段数对区间的修改和查询的操作就完全一致了。

题目:

P3384 【模板】重链剖分/树链剖分

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5;
ll n,m,r,p;
ll com;
ll head[N];
ll x,y,z;
ll deep[N],siz[N],father[N],son[N];
ll top[N];
ll a[N];
ll newa[N];
ll id[N];
ll cou;
ll res;
struct ed
{
	ll to,from;
}edge[N<<1];
ll cnt;
struct tr
{
	ll lazy;
	ll num;
}tree[N<<2];
void add(ll u,ll v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
void dfs1(ll x,ll fa)
{
	deep[x]=deep[fa]+1;
	siz[x]=1;
	father[x]=fa;
	for(ll i=head[x];i;i=edge[i].from)
	{
		ll v=edge[i].to;
		if(v!=fa)
		{
			dfs1(v,x);
			siz[x]+=siz[v];
			if(!son[x]||siz[v]>siz[son[x]])
			son[x]=v;
		}
	}
}
void dfs2(ll x,ll topx)
{
	cou++;
	top[x]=topx;
	id[x]=cou;
	newa[cou]=a[x]%p;
	if(!son[x])
	return ;
	dfs2(son[x],topx);
	for(ll i=head[x];i;i=edge[i].from)
	{
		ll v=edge[i].to;
		if(v!=father[x]&&v!=son[x])
		dfs2(v,v);
	}
}
//树链剖分 
void build(ll rt,ll l,ll r)
{
	tree[rt].lazy=0;
	if(l==r)
	{
		tree[rt].num=newa[l]%p;
		return ;
	}
	ll mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	tree[rt].num=(tree[rt<<1].num%p+tree[rt<<1|1].num%p)%p;
}
void pushdown(ll rt,ll l,ll r)
{
	ll mid=(l+r)>>1;
	tree[rt<<1].lazy=(tree[rt<<1].lazy+tree[rt].lazy)%p;
	tree[rt<<1|1].lazy=(tree[rt<<1|1].lazy+tree[rt].lazy)%p;
	tree[rt<<1].num=(tree[rt<<1].num%p+tree[rt].lazy*(mid-l+1))%p;
	tree[rt<<1|1].num=(tree[rt<<1|1].num%p+tree[rt].lazy*(r-mid))%p;
	tree[rt].lazy=0;
}
void addplus(ll rt,ll l,ll r,ll L,ll R,ll k)
{
	if(L<=l&&r<=R)
	{
		tree[rt].lazy+=k%p;
		tree[rt].num+=(k*(r-l+1))%p;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt,l,r);
	ll mid=(l+r)>>1;
	if(L<=mid)
	addplus(rt<<1,l,mid,L,R,k);
	if(R>mid)
	addplus(rt<<1|1,mid+1,r,L,R,k);
	tree[rt].num=(tree[rt<<1].num%p+tree[rt<<1|1].num%p)%p;
}
void query(ll rt,ll l,ll r,ll L,ll R)
{
	if(l>=L&&r<=R)
	{
		res+=tree[rt].num%p;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt,l,r);
	ll mid=(l+r)>>1;
	if(L<=mid)
	query(rt<<1,l,mid,L,R);
	if(R>mid)
	query(rt<<1|1,mid+1,r,L,R);
}
//区间操作 
void addrange(ll x,ll y,ll k)
{
	k%=p;
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<deep[top[y]])
		swap(x,y);
		addplus(1,1,n,id[top[x]],id[x],k);
		x=father[top[x]];
	}
	if(deep[x]>deep[y])
	swap(x,y);
	addplus(1,1,n,id[x],id[y],k);
}
void queryrange(ll x,ll y)
{
	ll ans=0;
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<deep[top[y]])
		swap(x,y);
		res=0;
		query(1,1,n,id[top[x]],id[x]);
		ans+=res%p;
		x=father[top[x]];
	}
	if(deep[x]>deep[y])
	swap(x,y);
	res=0;
	query(1,1,n,id[x],id[y]);
	ans+=res%p;
	cout<<ans%p<<endl;
}
//子树操作 
void addsontree(ll x,ll k)
{
	addplus(1,1,n,id[x],id[x]+siz[x]-1,k);
	return ;
}
void querysontree(ll x)
{
	res=0;
	query(1,1,n,id[x],id[x]+siz[x]-1);
	cout<<res%p<<endl;
}
int main()
{
	cin>>n>>m>>r>>p;
	for(ll i=1;i<=n;i++)
	cin>>a[i];
	for(ll i=1;i<n;i++)
	{
		ll u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	dfs1(r,0);
	dfs2(r,r);
	build(1,1,n);
	for(ll i=1;i<=m;i++)
	{
		cin>>com;
		if(com==1)
		{
			cin>>x>>y>>z;
			addrange(x,y,z);
		}
		if(com==2)
		{
			cin>>x>>y;
			queryrange(x,y);
		}
		if(com==3)
		{
			cin>>x>>z;
			addsontree(x,z);
		}
		if(com==4)
		{
			cin>>x;
			querysontree(x);
		}
	}
	return 0;
}

P3258 [JLOI2014] 松鼠的新家

比上面模版居然简单多了。。。线段树其实用不着建。。。(害我debug好久)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n;
int head[N];
int a[N],newa[N];
int id[N];
int father[N],deep[N],siz[N],son[N];
int top[N];
struct ee
{
	int to;
	int from;
}edge[N<<1];
int cnt;
int cou;
int res;
void add(int u,int v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
struct tr
{
	int lazy;
	int num;
}tree[N<<2];
void dfs1(int x,int fa)
{
	deep[x]=deep[fa]+1;
	father[x]=fa;
	siz[x]=1;
	for(int i=head[x];i;i=edge[i].from)
	{
		int v=edge[i].to;
		if(v==fa)
		continue;
		dfs1(v,x);
		siz[x]+=siz[v];
		if(!son[x]||siz[son[x]]<siz[v])
		son[x]=v;
	}
}
void dfs2(int x,int topx)
{
	top[x]=topx;
	cou++;
	id[x]=cou;
	if(!son[x])
	return ;
	dfs2(son[x],topx);
	for(int i=head[x];i;i=edge[i].from)
	{
		int v=edge[i].to;
		if(v!=son[x]&&v!=father[x])
		dfs2(v,v);
	}
}
void pushdown(int rt,int l,int r)
{
	int mid=(r+l)>>1;
	tree[rt<<1].lazy+=tree[rt].lazy;
	tree[rt<<1|1].lazy+=tree[rt].lazy;
	tree[rt<<1].num+=tree[rt].lazy*(mid-l+1);
	tree[rt<<1|1].num+=tree[rt].lazy*(r-mid);
	tree[rt].lazy=0;
}
void addplus(int rt,int l,int r,int L,int R,int k)
{
	if(l>=L&&r<=R)
	{
		tree[rt].num+=k*(r-l+1);
		tree[rt].lazy+=k;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt,l,r);
	int mid=(l+r)>>1;
	if(L<=mid)
	addplus(rt<<1,l,mid,L,R,k);
	if(R>mid)
	addplus(rt<<1|1,mid+1,r,L,R,k);
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
}
void query(int rt,int l,int r,int L,int R)
{
	if(l>=L&&r<=R)
	{
		res+=tree[rt].num;
		return ;
	}
	if(tree[rt].lazy)
	pushdown(rt,l,r);
	int mid=(l+r)>>1;
	res=0;
	if(L<=mid)
	query(rt<<1,l,mid,L,R);
	if(R>mid)
	query(rt<<1|1,mid+1,r,L,R);
}
void addrange(int x,int y,int k)
{
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<deep[top[y]])
		swap(x,y);
		addplus(1,1,n,id[top[x]],id[x],k);
		x=father[top[x]];
	}
	if(deep[x]>deep[y])
	swap(x,y);
	addplus(1,1,n,id[x],id[y],k);
}
void queryrange(int x)
{
	res=0;
	query(1,1,n,id[x],id[x]);
	cout<<res<<endl;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	for(int i=1;i<n;i++) 
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	dfs1(1,0);
	dfs2(1,1);
	for(int i=1;i<=n-1;i++)
	{
		addrange(a[i],a[i+1],1);
		addrange(a[i+1],a[i+1],-1);
	}
	for(int i=1;i<=n;i++)
	queryrange(i);
	return 0;
}

P2590 [ZJOI2008] 树的统计

一道模版的适度延伸,思路很清晰,

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
string com;
int x,y;
int q;
int head[N];
int top[N];
int father[N],son[N],deep[N],siz[N];
int res;
int id[N];
int a[N],newa[N];
struct ee
{
	int to,from;
}edge[N];
struct tr
{
	int maxx;
	int num;
}tree[N<<2];
int cnt;
int cou;
void add(int u,int v)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].from=head[u];
	head[u]=cnt;
}
void dfs1(int x,int fa)
{
	deep[x]=deep[fa]+1;
	siz[x]=1;
	father[x]=fa;
	for(int i=head[x];i;i=edge[i].from)
	{
		int v=edge[i].to;
		if(v==fa)
		continue;
		dfs1(v,x);
		siz[x]+=siz[v];
		if(!son[x]||siz[son[x]]<siz[v])
		son[x]=v;
	}
}
void dfs2(int x,int topx)
{
	top[x]=topx;
	cou++;
	id[x]=cou;
	newa[cou]=a[x];
	if(!son[x])
	return ;
	dfs2(son[x],topx);
	for(int i=head[x];i;i=edge[i].from)
	{
		int v=edge[i].to;
		if(v!=son[x]&&v!=father[x])
		dfs2(v,v);
	}
}
void build(int rt,int l,int r)
{
	if(l==r)
	{
		tree[rt].num=newa[l];
		tree[rt].maxx=newa[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
	tree[rt].maxx=max(tree[rt<<1].maxx,tree[rt<<1|1].maxx); 
}
void change(int rt,int l,int r,int x,int k)
{
	if(l==r)
	{
		tree[rt].num=k;
		tree[rt].maxx=k;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid)
	change(rt<<1,l,mid,x,k);
	else
	change(rt<<1|1,mid+1,r,x,k);
	tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
	tree[rt].maxx=max(tree[rt<<1].maxx,tree[rt<<1|1].maxx); 
}
void changenum(int x,int y)
{
	change(1,1,n,id[x],y);
	return ;
}
void q_sum(int rt,int l,int r,int L,int R)
{
	if(l>=L&&r<=R)
	{
		res+=tree[rt].num;
		return ;
	}
	int mid=(l+r)>>1;
	if(L<=mid)
	q_sum(rt<<1,l,mid,L,R);
	if(R>mid)
	q_sum(rt<<1|1,mid+1,r,L,R);
}
void querysum(int x,int y)
{
	res=0;
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<deep[top[y]])
		swap(x,y);
		q_sum(1,1,n,id[top[x]],id[x]);
		x=father[top[x]];
	}
	if(deep[x]>deep[y])
	swap(x,y);
	q_sum(1,1,n,id[x],id[y]);
	cout<<res<<endl;
}
int q_max(int rt,int l,int r,int L,int R)
{
	if(l>=L&&r<=R)
	return tree[rt].maxx;
	int mid=(l+r)>>1;
	if(L<=mid)
	res=max(res,q_max(rt<<1,l,mid,L,R));
	if(R>mid)
	res=max(res,q_max(rt<<1|1,mid+1,r,L,R));
	return res;
}
void querymax(int x,int y)
{
	res=-50000;
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<deep[top[y]])
		swap(x,y);
		res=max(res,q_max(1,1,n,id[top[x]],id[x]));
		x=father[top[x]];
	}
	if(deep[x]>deep[y])
	swap(x,y);
	res=max(res,q_max(1,1,n,id[x],id[y]));
	cout<<res<<endl;
}
int main()
{
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++)
	cin>>a[i];
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	cin>>q;
	for(int i=1;i<=q;i++)
	{
		cin>>com>>x>>y;
		if(com=="CHANGE")
		changenum(x,y);
		if(com=="QSUM")
		querysum(x,y);
		if(com=="QMAX")
		querymax(x,y);
	}
	return 0;
}

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值