树上差分(含链式向前星,LCA,树上点差分的主要知识)

目录

1,树上差分(点差分,边差分)

         1,点差分

          2,边差分

2,链式向前星(建图必备)

1,符号的意义

2,遍历公式:for (int i = head[u]; i; i = edge[i].next)

3,LCA(最近公共父节点)

倍增建图LCA代码

4,一道模板题:P3128 [USACO15DEC]Max Flow P


1,树上差分(点差分,边差分)

    1,点差分

 (注:LCA为u与v最近的公共父节点)

让u到v这段绿色曲线都+1,我们可以从俩边操作,即u+1,v+1,那么你lca及其上面的各个点就加多了,所以在lca-1,fa[lca]-1,lca减一,因为我还要lca+1的,只是他+2了,所以我-1,lca的父亲-1,因为lca前面的点是不要+1的,加上lca的-1,就抵消了俩边各自+1(合并就是+2)

2,边差分

思路差不多,但是我们是储存点的,所以我们可以把边的数据存到下面的点中,这样跟点差分差不多,不过,lca那个点表示的是他上面那条边(这条边我是不要的),只要黄色4条

 所以我是u+1,v+1,lca-2

2,链式向前星:参考链接:

三大图的储存——1,邻接矩阵,2,邻接表,3,链式向前星

3,LCA(最近公共父节点)

看过上图,大概你已经有思路了,我们已经铺垫好了,只需要用dfs去遍历点与点之间的关系,就能建图

倍增建图LCA代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 100;

struct node
{
	ll to, next;
} edge[N << 1]; //无向边记录俩个方向,记得开边界俩倍

ll dep[N];
ll pre[N][32];//dep记录当前点的深度,默认根节点深度(起始深度为1),pre[u][i]表示u的二进制(2^i)祖先,如pre[u][0]就是u的2^0祖先,即爸爸,用这个方法倍增,就可以不用一步一步慢慢走,时间从n变为logn

ll head[N];
ll num;

void add(ll f, ll t)//链式向前星加边
{
	edge[++num].next = head[f];
	edge[num].to = t;
	head[f] = num;
}

void dfs1(ll u, ll fa)//建树第一步,dfs1遍历从头到尾的点,依靠前面已经建边的关系,建立好各个点的深度
{
	//u为当前节点,fa为该节点的父亲
	dep[u] = dep[fa] + 1;//这就是为什么从头开始,这样深度就由浅到深可以一代一代继承
	pre[u][0] = fa;//显然把父子关系继承到pre祖先数组
	for (int i = 1; (1 << i) <= dep[u]; ++i) //二进制倍增,从2,4,8....填完祖先倍增关系
		{
			pre[u][i] = pre[pre[u][i - 1]][i - 1];//显然,u的2^i祖先等于(u的2^(i-1)祖先)的2^(i-1)祖先,如果不超过u的深度,就可以这么干
		}//(注意,每次走的步数都是从u这个点走,走2^i步,起点没有变)
	for (int i = head[u]; i; i = edge[i].next)
		{
			ll h = edge[i].to;
			if (h != fa)dfs1(h, u);//与u有联系的边,只要不是fa,那就是他的儿子,那就往深处dfs
		}
}

ll LCA(ll u, ll v)
{
	if (dep[u] > dep[v])swap(u, v); //始终让v深度大
	if (dep[v] > dep[u])//不相等时才求,如果相等求,会出现log2(0)=-INF
		{
			ll k = log2(dep[v] - dep[u]); //我们是倍增跑,所以n距离,只需要log2(n)
			for (int i = k; i >= 0; --i)
				{
					if (dep[pre[v][i]] >= dep[u])v = pre[v][i]; //只要v的深度还大于u,我们一定可以移动到俩者深度一样(因为二进制可以表示任何数)
				}
		}
	//出来u等于v,但是有可能v就是fca(u==v的话)
	if (u == v)return u; //是最近返回LCA
	ll k = log2(dep[v]); //不是,还是按深度走log2n步
	for (int i = k; i >= 0; --i)
		{
			if (pre[u][i] != pre[v][i]) //注意,我们是只有走下去不是祖先才走,因为你可能一步太大,走到LCA前面的公共点
				{
					u = pre[u][i];
					v = pre[v][i];
				}
		}
	//这样我们出来刚好到LCA的儿子那里
	return pre[v][0];//儿子的父亲才是LCA
}
int main()
{
	ll n, k;
	cin >> n;
	ll f, t;

	for (int i = 1; i <= n; ++i)
		{
			cin >> f >> t;
			add(f, t);
			add(t, f);
		}

	dfs1(1, 0);

	for (int i = 1; i <= k; ++i)
		{
			cin >> f >> t;
			ll lca = LCA(f, t);
		}
}

4,一道模板题:P3128 [USACO15DEC]Max Flow P

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 100;

struct node
{
	ll to, next;
} edge[N << 1]; //无向边记录俩个方向,记得开边界俩倍

ll dep[N];
ll pre[N][32];//dep记录当前点的深度,默认根节点深度(起始深度为1),pre[u][i]表示u的二进制(2^i)祖先,如pre[u][0]就是u的2^0祖先,即爸爸,用这个方法倍增,就可以不用一步一步慢慢走,时间从n变为logn

ll head[N];
ll num;

ll point[N];
ll ans;

void add(ll f, ll t)//链式向前星加边
{
	edge[++num].next = head[f];
	edge[num].to = t;
	head[f] = num;
}

void dfs1(ll u, ll fa)//建树第一步,dfs1遍历从头到尾的点,依靠前面已经建边的关系,建立好各个点的深度
{
	//u为当前节点,fa为该节点的父亲
	dep[u] = dep[fa] + 1;//这就是为什么从头开始,这样深度就由浅到深可以一代一代继承
	pre[u][0] = fa;//显然把父子关系继承到pre祖先数组
	for (int i = 1; (1 << i) < dep[u]; ++i)//二进制倍增,从2,4,8....填完祖先倍增关系
		{
			pre[u][i] = pre[pre[u][i - 1]][i - 1];//显然,u的2^i祖先等于(u的2^(i-1)祖先)的2^(i-1)祖先,如果不超过u的深度,就可以这么干
		}//(注意,每次走的步数都是从u这个点走,走2^i步,起点没有变)
	for (int i = head[u]; i; i = edge[i].next)
		{
			ll h = edge[i].to;
			if (h != fa)dfs1(h, u);//与u有联系的边,只要不是fa,那就是他的儿子,那就往深处dfs
		}
}

ll LCA(ll u, ll v)
{
	if (dep[u] > dep[v])swap(u, v); //始终让v深度大
	ll k = log2(dep[v] - dep[u]); //我们是倍增跑,所以n距离,只需要log2(n)
	for (int i = k; i >= 0; --i)
		{
			if (dep[pre[v][i]] >= dep[u])v = pre[v][i]; //只要v的深度还大于u,我们一定可以移动到俩者深度一样(因为二进制可以表示任何数)
		}
	//出来u等于v,但是有可能v就是fca(u==v的话)
	if (u == v)return u; //是最近返回LCA
	k = log2(dep[v]); //不是,还是按深度走log2n步
	for (int i = k; i >= 0; --i)
		{
			if (pre[u][i] != pre[v][i]) //注意,我们是只有走下去不是祖先才走,因为你可能一步太大,走到LCA前面的公共点
				{
					u = pre[u][i];
					v = pre[v][i];
				}
		}
	//这样我们出来刚好到LCA的儿子那里
	return pre[v][0];//儿子的父亲才是LCA
}
void dfs(ll u, ll fa)
{
	for (int i = head[u]; i; i = edge[i].next)
		{
			ll h = edge[i].to;
			if (h != fa)
				{
					dfs(h, u);
					//是遍历回来(已经累加过子代的值,祖先才加)
					point[u] += point[h];//这里有括号,每次dfs回来都要加一次
				}
		}
	ans = max(ans, point[u]);
}

int main()
{
	ll n, k;
	cin >> n >> k;
	ll f, t;
	for (int i = 1; i <= n - 1; ++i)
		{
			cin >> f >> t;
			add(f, t);
			add(t, f);
		}
	dfs1(1, 0);
	for (int i = 1; i <= k; ++i)
		{
			cin >> f >> t;
			ll lca = LCA(f, t);
			point[f]++;
			point[t]++;
			point[lca]--;
			point[pre[lca][0]]--;
		}
	dfs(1, 0); //这个dfs求ans
	cout << ans << endl;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值