专题·树上差分【including 例题:洛谷·bzoj·最大流Max Flow, 洛谷· [JLOI2014]松鼠的新家

初见安~本专题/题解 是有前置知识的哦:LCA

树上差分——顾名思义就是把差分运用到树上。【啪!

本来差分是相邻作差,可以用于求数轴上被线段覆盖的最大层数。求解时可以在线段的左短点和右端点+1处标记+1和-1。

树上差分也是同样的道理,我们可以先看一个例题来讲解:

传送门:洛谷P3128

题目描述

Farmer John has installed a new system of N-1N−1 pipes to transport milk between the NN stalls in his barn (2 \leq N \leq 50,0002≤N≤50,000), conveniently numbered 1 \ldots N1…N. Each pipe connects a pair of stalls, and all stalls are connected to each-other via paths of pipes.

FJ is pumping milk between KK pairs of stalls (1 \leq K \leq 100,0001≤K≤100,000). For the iith such pair, you are told two stalls s_isi​ and t_iti​, endpoints of a path along which milk is being pumped at a unit rate. FJ is concerned that some stalls might end up overwhelmed with all the milk being pumped through them, since a stall can serve as a waypoint along many of the KK paths along which milk is being pumped. Please help him determine the maximum amount of milk being pumped through any stall. If milk is being pumped along a path from s_isi​to t_iti​, then it counts as being pumped through the endpoint stalls s_isi​ and

t_iti​, as well as through every stall along the path between them.

FJ给他的牛棚的N(2≤N≤50,000)个隔间之间安装了N-1根管道,隔间编号从1到N。所有隔间都被管道连通了。

FJ有K(1≤K≤100,000)条运输牛奶的路线,第i条路线从隔间si运输到隔间ti。一条运输路线会给它的两个端点处的隔间以及中间途径的所有隔间带来一个单位的运输压力,你需要计算压力最大的隔间的压力是多少。

输入格式:

The first line of the input contains NN and KK.

The next N-1N−1 lines each contain two integers xx and yy (x \ne yx≠y) describing a pipe

between stalls xx and yy.

The next KK lines each contain two integers ss and tt describing the endpoint

stalls of a path through which milk is being pumped.

输出格式:

An integer specifying the maximum amount of milk pumped through any stall in the

barn.

输入样例#1: 

5 10
3 4
1 5
4 2
5 4
5 4
5 4
3 5
4 3
4 3
1 3
3 5
5 4
1 5
3 4

输出样例#1:

9

题解:

这就是一个树上差分的裸题,题意我们可以看出其实和线段上的差分是类似的。

这个题我们可以同样用+1和-1来考虑——假设当前的运输路径是从u->v

那么这条路径上的点权都要+1。一般树上的操作都是要dfs到底然后回溯操作的,所以结合查分的思想,我们可以设一个查分数组tot,然后tot[ u ]++,tot[ v ]++;这样一来就表示自下而上从u、从v开始多了一层覆盖;很明显在两点的lca处重复覆盖了一层,所以要tot[ lca ]--;当然这层覆盖是要去掉的,我们还要在tot[ fa[ lca ] ] --。也就是:

然后这个问题就解决完了!!!最后dfs扫一遍得出最大的tot就行了。

下面上代码——
 

#include<bits/stdc++.h>
#define maxn 50005
using namespace std;
int n, m;
int read()
{
	int x = 0, ch = getchar();
	while(!isdigit(ch)) ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x;
}

vector<int> t[maxn];
int ans = 0, tot[maxn];
int fa[maxn], dep[maxn], size[maxn], top[maxn], son[maxn];
struct hld//这里选择了用树剖求LCA,就打个包了
{
	void dfs1(int u)
	{
		register int v;
		size[u] = 1;
		for(int i = 0; i < t[u].size(); i++)
		{
			v = t[u][i]; if(v == fa[u]) continue;
			dep[v] = dep[u] + 1, fa[v] = u;
			dfs1(v);
			size[u] += size[v];
			if(size[v] > size[son[u]]) son[u] = v;
		}
	}
	
	void dfs2(int u, int tp)
	{
		register int v;
		top[u] = tp;
		if(son[u]) dfs2(son[u], tp);
		for(int i = 0; i < t[u].size(); i++)
		{
			v = t[u][i];
			if(v != fa[u] && v != son[u]) dfs2(v, v);
		}
	}
	
	int ask(int u, int v)
	{
		while(top[u] != top[v])
		{
			if(dep[top[u]] > dep[top[v]]) swap(u, v);
			v = fa[top[v]];
		}
		return dep[u] > dep[v]? v : u;
	}
}HLD;

void dfs(int u)//最后扫一遍得出答案
{
	register int v;
	for(int i = 0; i < t[u].size(); i++)
	{
		v = t[u][i];
		if(v == fa[u]) continue;
		dfs(v);
		tot[u] += tot[v];
	}
	ans = max(ans, tot[u]);
//	printf("tot:%d %d\n", u, tot[u]);
}

int main()
{
	memset(tot, 0, sizeof tot);
	n = read(), m = read();
	register int u, v, lca;
	for(int i = 1; i < n; i++)
		u = read(), v = read(), t[u].push_back(v), t[v].push_back(u);
		
	HLD.dfs1(1), HLD.dfs2(1, 1);//树剖初始化
	
	for(int i = 1; i <= m; i++)
	{
		u = read(), v = read();
		tot[u]++, tot[v]++;//标记
		lca = HLD.ask(u, v);
		tot[lca]--, tot[fa[lca]]--;//标记
		if(lca == 1) tot[1]--;//特殊处理,因为在0点操作没有意义
	}
	dfs(1);
	printf("%d\n", ans);
	return 0;
}

思路还是很简单的,树上差分也是很巧妙的。

还有个水题就是松鼠的新家【传送门:洛谷P3258

题目描述

松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在”树“上。

松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去a1,再去a2,......,最后到an,去参观新家。可是这样会导致维尼重复走很多房间,懒惰的维尼不停地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。

维尼是个馋家伙,立马就答应了。现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。

因为松鼠参观指南上的最后一个房间an是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。

输入格式:

第一行一个整数n,表示房间个数第二行n个整数,依次描述a1-an

接下来n-1行,每行两个整数x,y,表示标号x和y的两个房间之间有树枝相连。

输出格式:

一共n行,第i行输出标号为i的房间至少需要放多少个糖果,才能让维尼有糖果吃。

输入样例#1: 

5
1 4 5 3 2
1 2
2 4
2 3
4 5

输出样例#1: 

1
2
1
2
1

说明:

2<= n <=300000

题解:

这个题也是一个树上差分的裸题——不管其他的限制条件,翻译过来就是求每个点被路径覆盖的层数。所以操作和前一个题几乎一模一样!!!求LCA然后n - 1个线段差分覆盖就行了。

所以现在我们来处理细节问题——因为我们从第二次观光【a2~a3】开始,每个起点都是在作为终点的时候计算过一次的,所以答案都要-1;至于an是餐厅,我们就直接2~n都-1就可以了。第一个点也是要放糖的,就不动了。

我最后是卡在了一个很愚蠢的细节——不一定是从1号房间开始啊!!!没有减的那个房间不应该是1号,而是a1号!!!

好了,上代码了:)

#include<bits/stdc++.h>
#define maxn 300005
using namespace std;
int n;
int read()
{
	int x = 0, ch = getchar();
	while(!isdigit(ch)) ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x;
}

vector<int> t[maxn];//同样的,关注的是点权,所以可以直接用vector
int a[maxn], tot[maxn];
int dep[maxn], son[maxn], size[maxn], top[maxn], fa[maxn];
struct hld//老样子,树剖求LCA
{
	void dfs1(int u)
	{
		register int v;
		size[u] = 1;
		for(int i = 0; i < t[u].size(); i++)
		{
			v = t[u][i]; if(v == fa[u]) continue;
			fa[v] = u; dep[v] = dep[u] + 1;
			dfs1(v);
			size[u] += size[v];
			if(size[v] > size[son[u]]) son[u] = v;
		}
	}
	
	void dfs2(int u, int tp)
	{
		register int v;
		top[u] = tp;
		if(son[u]) dfs2(son[u], tp);
		for(int i = 0; i < t[u].size(); i++)
		{
			v = t[u][i];
			if(v != fa[u] && v != son[u]) dfs2(v, v);
		}
	}
	
	int ask(int u, int v)
	{
		while(top[u] != top[v])
		{
			if(dep[top[u]] > dep[top[v]]) swap(u, v);
			v = fa[top[v]];
		}
		return dep[u] > dep[v]? v : u;
	}
}HLD;

void dfs(int u)
{
	register int v;
	for(int i = 0; i < t[u].size(); i++)
	{
		v = t[u][i];
		if(v == fa[u]) continue;
		dfs(v);
		tot[u] += tot[v];//直接累加就可以了
	}
}

int main()
{
	n = read();
	register int u, v;
	for(int i = 1; i <= n; i++) a[i] = read();
	for(int i = 1; i < n; i++)
		u = read(), v = read(), t[u].push_back(v), t[v].push_back(u);
		
	HLD.dfs1(1); HLD.dfs2(1, 1);
	
	for(int i = 1; i < n; i++)
	{
		tot[a[i]]++, tot[a[i + 1]]++;
		register int lca = HLD.ask(a[i], a[i + 1]);
		tot[lca]--; tot[fa[lca]]--;//树上差分基础操作
	}
	
	dfs(1);
	tot[a[1]]++;//提前+1是为了让-1的操作对它没有影响
	for(int i = 1; i <= n; i++)
		printf("%d\n", tot[i] - 1);
		
	return 0;
}

最后还有一个毒瘤题,可以在多练几个难度大一些的题【传送门建设中】后刷一下:天天爱跑步

迎评:)
——End——

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值