树上差分-LCA

差分的基本思想详情见博客(一维、二维差分):
https://blog.csdn.net/weixin_45629285/article/details/111146240

算法分析:

面向的对象可以是树上的结点,也可以是树上的边
结点表述方式:
问题:
给定一棵有N个点的树,所有节点的权值初始时都为0。
有K次操作,每次指定两个点s,t,将s到t路径上所有点的权值都+c,求最后树上每个结点的权值。
在这里插入图片描述

求解思路:
对于每一次修改s,t,将s,t的权值+c;
将LCA(s,t)和father[LCA(s,t)]的权值-c;
K次操作后每个结点最终被覆盖的次数(最终的权值)就是这个点所在子树的权值和。

在这里插入图片描述

边表述方式:
问题:
给定一棵有N个点的树,或者为有N-1条边将任意两个点通过路径连接起来的无向图,所有边的权值初始时都为0。
有K次操作,每次指定两个点s,t,将s到t路径上所有边的权值都+c,求最后树上每个边的权值。
在这里插入图片描述

求解思路:
对于每一次修改结点s,t,将s,t的权值+c;
将LCA(s,t)的权值-2*c;
K次操作后每个边最终被覆盖的次数(最终的权值)就是这个边下面的结点所在子树的权值和。

在这里插入图片描述
注意,两种方式的表示方法的求解思路有一点小的不同,但是我们只要保证一个思想,就是需要增加的才增加,不需要改变的就要减去
其中求LCA有三种方法:向上标记法、倍增法和tarjan离线算法,具体参考博客:https://blog.csdn.net/m0_58642116/article/details/128550161?spm=1001.2014.3001.5501

练习例题

链接:
https://www.acwing.com/problem/content/description/354/
分析:
1.主要边就构成了一棵树,附加边其实就是非树边,读题的时候就要能读出来,转化一下意思
2.一个附加边如果与一些主要边组成了环,这个环上的所有主要边砍断的同时也需要砍断这个附加边,也就相当于这些主要边砍断树的可能性都加一;如果主要边没有跟附加边组成环,那直接砍断这个主要边就能使得图不连通;但是如果主要边与多个附加边组成了环,砍断了当前主要边,再砍一个附加边没法使图不连通
3.最终也就是转化为了树上两个结点之间路径的问题,转化为树上差分问题
任意一个附加边,将附加边两端结点(s,t)间的树边的权值都加一,也就是s权值+1,t权值+1,LCA(s,t)-2;
AC代码:

#include<iostream>
#include<vector>
#include<queue>
#include<string.h>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,u,v,ans=0;
const int N=100005;
vector<int>tr[N]; 
int depth[N],fa[N][17];
int wei[N];  //树中每个结点的权值 

void bfs(int s)
{
	queue<int>que;
	memset(depth,INF,sizeof(depth));
	que.push(s);
	depth[0]=0;depth[1]=1;
	while(!que.empty())
	{
		int u=que.front();
		que.pop();
		
		for(int i=0;i<tr[u].size();i++)
		{
			int v=tr[u][i];
			if(depth[v]==INF)
			{
				depth[v]=depth[u]+1;
				que.push(v);
				
				fa[v][0]=u;
				for(int k=1;k<=16;k++)
				{
					fa[v][k]=fa[fa[v][k-1]][k-1]; 
				} 
			}
		}
	}
}
int LCA(int x,int y)
{
	if(depth[x]<depth[y]) swap(x,y);
	for(int k=16;k>=0;k--)
	{
		if(depth[fa[x][k]]>=depth[y])
		{
			x=fa[x][k];
		}
	}
	if(x==y) return x;
	for(int k=16;k>=0;k--)
	{
		if(fa[x][k]!=fa[y][k])
		{
			x=fa[x][k];
			y=fa[y][k];
		}
	}
	return fa[x][0];
} 

int dfs(int s,int fa)
{
	//以s为根节点的子树的权值 
	int num=0;
	for(int i=0;i<tr[s].size();i++)
	{
		if(tr[s][i]==fa) continue;
		num+=dfs(tr[s][i],s);
	}
	
	//再加上边下面的这个结点的权值(当前子树根节点权值) 
	num+=wei[s];
	
	if(s==1)  return num;  //跟结点上面的那个边不存在,直接返回 
	
	if(num==0)  ans+=m;   //当前主要边(树边)没有与任何附加边(非树边)组成环,直接砍断就能使得图分成两半,附加边就随便砍哪条都行 
	else if(num==1) ans+=1;   //当前主要边与一条 附加边组成环,要想使得图不连通砍断这个边的同时还要砍断那条附加边 
	//num>=2时,主要边与多个附加边组成了环,砍断了当前主要边,再砍一个附加边没法使图不连通 
	return num;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<n;i++)
	{
		cin>>u>>v;
		tr[u].push_back(v);
		tr[v].push_back(u);
	}
	bfs(1);  //倍增法初始化depth、fa数组
	
	//直接输入遍历非树边(附加边)
	for(int i=0;i<m;i++)
	{
		cin>>u>>v;
		int lca=LCA(u,v);
	//	cout<<lca<<endl;
		wei[u]+=1;  //差分求权值
		wei[v]+=1;
		wei[lca]-=2;
	} 
	//遍历整棵树差分法求每条边的权值,并对树边进行讨论求方法数
	dfs(1,-1);
	cout<<ans<<endl;
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值