LCA模板 与相关知识

这篇博客介绍了如何利用前缀和、差分更新等方法来优化树上的最近公共祖先(LCA)查询,以及在树上进行路径最小值和差分操作的高效算法。通过预处理节点的深度和父节点信息,可以实现O(lgn)的时间复杂度查询。此外,还展示了如何处理树上两点间路径的最小值和树上差分问题。
摘要由CSDN通过智能技术生成

一次查询时间O(lgn)

预处理:

 求每一个节点的深度与父节点


int f[N][20],dep[N];
V<pii>h[N];
int n;
inline void dfs(int u, int fa)
{
	f[u][0] = fa;
	dep[u] = dep[fa] + 1;
	for (auto v : h[u])
	{
		if (v.first == fa)continue;
		dfs(v.first, u);
	}
}
//
inline int lca(int x, int y)
{
	if (dep[x] < dep[y])
		swap(x, y);
	int d = dep[x] - dep[y];
	for(int j=M;j>=0;j--)
		if (d & (1 << j))
			x = f[x][j];
	if (x == y)
		return x;
	for (int i =M; i >= 0; i--)
		if (f[x][i] != f[y][i])
		{
			x = f[x][i], y = f[y][i];
		}
	return f[x][0];
}


int main()
{
	int q;
	scanf("%d%d", &n, &q);
	for (int i = 1; i < n; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		h[u].push_back({ v,w });
		h[v].push_back({ u,w });
	}
	dfs(1, 0);
for (int j = 1; j<=M; j++)
	for (int u = 1; u <= n; u++)
	{
		f[u][j] = f[f[u][j - 1]][j - 1];
	}
	while (q--)
	{
		int u, v; scanf("%d%d", &u, &v);
		printf("%d\n", lca(u, v));
	}
}

前缀和lca   (后缀和 lca 同理) 

int f[ N ];

f[ i ] 表示 1~i个点的  lca

f[ 1 ]=a[ 1 ];

for(int i=2;i<=n;i++)

      f[i]=lca( f[ i-1 ] ,a[ i ]);

 

树上两点间的路径最小值 

97a6dd0f14ee4f8b9b5751a0e98cb9da.png

int f[N][20], dep[N];
V<pii>h[N];
int n;
int res[N][20];


inline void dfs(int x, int fa)
{
	f[x][0] = fa;
	dep[x] = dep[fa] + 1;
	for (pii w : h[x])
	{
		if (w.first == fa)continue;
		res[w.first][0] = w.second;
		dfs(w.first, x);
	}
}
inline int lca(int x, int y)
{
	int ans = 0x3f3f3f3f;
	if (dep[x] < dep[y])
		swap(x, y);
	int d = dep[x] - dep[y];
	for (int j = 18; j >= 0; j--)
		if (d & (1 << j))
		{
			ans = min(ans, res[x][j]);
			x = f[x][j];
		}
	if (x == y)
		return ans;
	for (int i = 18; i >= 0; i--)
		if (f[x][i] != f[y][i])
		{
			ans = min(ans, min(res[x][i], res[y][i]));
			x = f[x][i], y = f[y][i];
		}
	return ans = min(ans, min(res[x][0], res[y][0]));
}
int main()
{
	int q;
	scanf("%d%d", &n, &q);
	for (int i = 1; i < n; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		h[u].push_back({ v,w });
		h[v].push_back({ u,w });
	}
	dfs(1, 0);
	for (int j = 1; j <= 18; j++)
     	for (int u = 1; u <= n; u++)
		{
			f[u][j] = f[f[u][j - 1]][j - 1];
			res[u][j] = min(res[u][j - 1], res[f[u][j - 1]][j - 1]);
		}
	while (q--)
	{
		int u, v; scanf("%d%d", &u, &v);
		printf("%d\n", lca(u, v));
	}
}

树上差分:

ea15e5c9070541a5a658e3b37d426bd7.png

int f[N][20], dep[N];
V<int>h[N];
int n;
 // tag[i] 记录差分 val[i]记录 每次差分后 为了消除其他影响 而记录的值
int a[N],tag[N],val[N];
inline void dfs1(int x, int fa)
{
	f[x][0] = fa;
	dep[x] = dep[fa] + 1;
	for (auto v : h[x])
	{
		if (v == fa)continue;
		dfs1(v, x);
	}
}
//
inline int Lca(int x, int y)
{
	if (dep[x] < dep[y])
		swap(x, y);
	int d = dep[x] - dep[y];
	for (int j = 18; j >= 0; j--)
		if (d & (1 << j))
			x = f[x][j];
	if (x == y)
		return x;
	for (int i = 20; i >= 0; i--)
		if (f[x][i] != f[y][i])
		{
			x = f[x][i], y = f[y][i];
		}
	return f[x][0];
}

// 遍历 tag数组 对于差分数字求和
void dfs2(int u, int fa)
{
	for (auto v : h[u])
	{
		if (v == fa)continue;
		dfs2(v, u);
		tag[u] += tag[v];
	}
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i < n; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		h[u].push_back(v);
		h[v].push_back(u);
	}
	dfs1(1, 0);
	for (int j = 1; j <= 18; j++)
		for (int u = 1; u <= n; u++)
		{
			f[u][j] = f[f[u][j - 1]][j - 1];
		}
	for (int i = 2; i <= n; i++)
	{
		int u = a[i - 1], v = a[i];
		int lca = Lca(u, v);
	
		tag[u]++;
		tag[v]++;
      // 在 lca这个节点以上的节点中 没有了差分贡献 但自身值需要加1 
		tag[lca] -= 2;
		val[lca]++;
		//  u->v->x  路径中 下一次时候 v又被加过一次 此次需要-1
		//同时 题意中 在访问最后an的时候 也需要-1 
		val[v]--;
	}
	dfs2(1, 0);
	for (int i = 1; i <= n; i++)
		cout << val[i] + tag[i] << "\n";
}

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zzz0929_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值