LCA模板解释与树上差分介绍

4 篇文章 0 订阅

倍增求LCA

对于树上求两个节点的最小公共祖先,很明显,可以通过从这两个点一步步往上走,得到一个相同的节点,那么这个节点就是所要求的最小公共祖先了。但是,这种暴力,明显会T。那么,既然我们可以通过一步步往上爬,是不是也可以两步两步往上爬,三步三步,甚至一百步一百步呢?
记得杰伦有首歌叫《蜗牛》-----我要一步一步往上爬
既然有这样一种一次往上爬多步的思想在了,那么,距离一次是爬几步呢?我们发现,所有的数都可以由任意个不同的2的幂次方组成,所以,可以每次往上爬不同的2的幂次方。这就是倍增求LCA的主要思想了。这和ST表思想一样。
代码:
// 最终模板
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=5e5+5;
int n,m,s,u,v,a,b;
int d[N],p[N][31];
int cnt,head[N];
struct edge{int next,to;}e[M<<1];

inline void add(int u,int v)
{
    cnt++;
    e[cnt].next=head[u];
    e[cnt].to=v;
    head[u]=cnt;	
}

inline void dfs(int u,int fa)
{
	// p[i][j]表示i节点向上2^j层后到达的节点 
    for (register int i=1; (1<<i)<=d[u]; ++i) p[u][i]=p[p[u][i-1]][i-1];
    for (register int i=head[u]; i; i=e[i].next)
    if (e[i].to!=fa)
    {
        d[e[i].to]=d[u]+1;
        p[e[i].to][0]=u;
        dfs(e[i].to,u);
    }
}

inline int lca(int a,int b)
{
    if (d[a]>d[b]) swap(a,b);  //强制让b节点向上移动 
    for (register int i=20; i>=0; --i) if (d[b]-(1<<i)>=d[a]) b=p[b][i];
    // 如果,位于下面的b节点,向上移动(1<<i)的层数,还是>=a的层数的话,就把b节点向上移动 
    if (a==b) return a;
    for (register int i=20; i>=0; --i)
    if (p[a][i]==p[b][i]) continue;    //如果这次的这个祖先一样,就找下一个祖先,写个continue,更有层次感 
    else a=p[a][i],b=p[b][i];          //如果这次的祖先不一样,那么就把这两个点都向上移动 
    return p[a][0];
}

int main(){
    scanf("%d%d%d",&n,&m,&s);
    for (register int i=1; i<n; ++i) scanf("%d%d",&u,&v),add(u,v),add(v,u);
    dfs(s,0);
    for (register int i=1; i<=m; ++i)
    {
        scanf("%d%d",&a,&b);
        printf("%d\n",lca(a,b));	
    }
return 0;
}
学会LCA模板,就可以解决很多的模板了。其中,就有树上差分。

树上点差分

相信大家都知道线性差分的写法、作用与适用范围。差分就是一种能解决修改多次,询问较少次问题的简单数据结构,除了线性差分,还有树上差分。
点差分:给你一棵树,每个节点都有相应的权值,然后给你若干个修改,每次把从u到v的路径上的节点权值都加上w,问最终每个节点权值是多少。
代码:
// 对于点差分来说,每次修改时候公式为:
// f[a]++,f[b]++,f[lca(a,b)]--, f[p[lca(a,b)][0]]--

#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int n,m,u,v,x,y,LCA,ans;
int d[N],p[N][21],f[N];
int cnt,head[N];
struct edge{int next,to;}e[N<<1];

inline void add(int u,int v)
{
	cnt++;
	e[cnt].next=head[u];
	e[cnt].to=v;
	head[u]=cnt;		
}

void dfs(int u,int fa)
{
	for (register int i=1; (1<<i)<=d[u]; ++i) p[u][i]=p[p[u][i-1]][i-1];
	for (register int i=head[u]; i; i=e[i].next)
	if (e[i].to!=fa)
	{
		d[e[i].to]=d[u]+1;
		p[e[i].to][0]=u;
		dfs(e[i].to,u);
	}
}

inline int lca(int a,int b)
{
	if (d[a]>d[b]) swap(a,b);
	for (register int i=20; i>=0; --i) if (d[b]-(1<<i)>=d[a]) b=p[b][i];
	if (a==b) return a;
	for (register int i=20; i>=0; --i)
	if (p[a][i]==p[b][i]) continue;
	else a=p[a][i],b=p[b][i];
	return p[a][0];	
}

void dfs1(int u,int fa)
{
	for (register int i=head[u]; i; i=e[i].next)
	if (e[i].to!=fa)
	{
		dfs1(e[i].to,u);
		f[u]+=f[e[i].to];
	}
	ans=max(ans,f[u]);
}

int main(){
	scanf("%d%d",&n,&m);
	for (register int i=1; i<n; ++i) scanf("%d%d",&u,&v),add(u,v),add(v,u);
	dfs(1,0);
	for (register int i=1; i<=m; ++i)
	{
		scanf("%d%d",&x,&y);
		LCA=lca(x,y);
		f[x]++; f[y]++; f[LCA]--; f[p[LCA][0]]--;
	}
	dfs1(1,0);
//	puts(""); for (register int i=1; i<=n; ++i) printf("%d ",f[i]); puts("");
	printf("%d\n",ans);
return 0;
}
推荐题目:
https://www.luogu.org/problemnew/show/P3128
https://www.luogu.org/problemnew/show/P3258
注意,对于一开始的权值,应该先把这个权值记录,在修改的差分数组全部做完后再在答案数组上累加原有的权值。

树上边差分

与点差分大同小异,只要把边权压到点权利即可,原来的f[i]表示节点i的权值,现在的f[i]表示节点i的父亲节点到i节点的距离。其中,f[根]=0。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int n,m,u,v,w,x,y,LCA,ans;
int d[N],p[N][21],f[N],ff[N];
int cnt,head[N];
struct edge{int next,to,w;}e[N<<1];

inline void add(int u,int v,int w)
{
	cnt++;
	e[cnt].next=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;		
}

void dfs(int u,int fa,int w)
{
	ff[u]=w;
	for (register int i=1; (1<<i)<=d[u]; ++i) p[u][i]=p[p[u][i-1]][i-1];
	for (register int i=head[u]; i; i=e[i].next)
	if (e[i].to!=fa)
	{
		d[e[i].to]=d[u]+1;
		p[e[i].to][0]=u;
		dfs(e[i].to,u,e[i].w);
	}
}

inline int lca(int a,int b)
{
	if (d[a]>d[b]) swap(a,b);
	for (register int i=20; i>=0; --i) if (d[b]-(1<<i)>=d[a]) b=p[b][i];
	if (a==b) return a;
	for (register int i=20; i>=0; --i)
	if (p[a][i]==p[b][i]) continue;
	else a=p[a][i],b=p[b][i];
	return p[a][0];	
}

void dfs1(int u,int fa)
{
	for (register int i=head[u]; i; i=e[i].next)
	if (e[i].to!=fa)
	{
		dfs1(e[i].to,u);
		f[u]+=f[e[i].to];
	}
	ans=max(ans,f[u]);
}

int main(){
	//f[i]表示i节点的父亲到i节点的距离
	scanf("%d",&n);
	for (register int i=1; i<n; ++i) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
	dfs(1,0,0);
	scanf("%d",&m);
	for (register int i=1; i<=m; ++i)
	{
		scanf("%d%d",&x,&y);
		LCA=lca(x,y);
		f[x]++; f[y]++; f[LCA]-=2;
	}
	dfs1(1,0);
	for (register int i=1; i<=n; ++i) f[i]+=ff[i];
	for (register int i=1; i<=n; ++i) printf("%d ",f[i]); puts("");
return 0;
}
推荐题目:https://www.luogu.org/problemnew/show/P2680
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值