洛谷 P3128 [USACO15DEC]Max Flow P

2 篇文章 0 订阅
2 篇文章 0 订阅

缘起

树上差分还是学了吧~ 人傻就要多读书~ 嘿嘿 洛谷 P3128 [USACO15DEC]Max Flow P

树上差分我学习了【2】,秒懂~ 和树剖一样,树上差分还是蛮简单的东西~

分析

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

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

【输入】
第一行 是n,k, 然后n-1行是树的边
然后k行每行s,t

【输出】
最大压力

【样例输入】
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

【样例输出】
9

回顾一下【1】,

差分是什么?

就是利用前缀和思想,将一个数组a的全部元素的信息融入到另一个数组b中去了.

为什么要差分优化?

因为一旦使用差分处理了a, 那么a上的[l, r]O(n)的区间操作就转换为了b上的 O(1)的b[l]、b[r+1]的操作. 特别适用于全部修改操作在全部询问前面的问题.

树上差分是什么?

不就是把线性数据结构(其实就是数组)上的差分处理搬到树上去了么?

差分的题面一般是给一堆 [l, r]上的区间操作,然后询问. 而数组上的区间对应到树上不就是树上两个点之间的唯一的路径么? 所以树上差分的题面一般是给一堆树上两个点之间的路径上的操作,然后询问.

就本题而言, 不就是给你n多树上路径s–t, 路径上的

和树剖一样(【3】、【4】),树上差分也分为点差分和边差分. 而通过【3】、【4】的学习我们知道,点树剖其实关键就是点权,而边树剖的关键就是每个点通往它的父节点的树边的权. 所以搬到树上差分也是一样的.

下面讲解

点差分

点差分最典型的题面是(其实就是本题)

给你一棵树, 然后给你n多树上路径, 最后问你树上每个点被这些路径覆盖的次数?

对每一条树上路径u–v,令a = lca(u, v) ((lca不懂的童鞋参见【5】))如果对每条路径都去先求lca(u), 然后暴力统计u到a、v到a上的点被覆盖的次数, 那么妥妥被T飞啊~

等等~ 上面这种暴力操作不就类似于数组区间[l, r]全部加上1么? 我们怎么优化的? 差分啊~

令 b[i] 是一个0数组(i是顶点). 然后对于树上路径u–v, 进行下面的操作

++b[u];
++b[v];
--b[a];
--b[fa[a]];

注意,对于每条树上路径u–v, 我们的操作都是O(1)的. 经过这些预处理之后,最后统计每个点被覆盖的次数只需要进行简单的dfs即可~

void dfs(int cur)
{
    for(int h = head[cur], to; ~h; h = g[h].nxt)
    {
        to = g[h].to;
        dfs(to);
        b[cur] += b[to];
    }
}

// 调用方法
dfs(root);

最后b[i]就是顶点i被覆盖的次数。

为什么上面的算法是正确的?

在这里插入图片描述

因为u–v 只会让u–a、a–v 这两条链上的节点被覆盖次数+1,要杜绝自fa[a]往上被覆盖的次数. 所以b[u]++保证了u–a每个节点被覆盖的次数+1(这一点你去观照上面的dfs算法), 而b[v]++ 保证了 v–a 每个节点被覆盖的次数+1,但是这样a被覆盖的次数就+2了~ 所以才要 b[a]–,这样最后a被覆盖的次数就仅仅+1,这正是我们想要的效果 那么怎么阻止fa[a]往上的节点被覆盖的次数有所新增呢? 显然就只需要 b[fa[a]]-- 即可(因为b[a]最后的效果是自增1, 所以只需要阻止这个1往上传播即可, 所以只需要让b[fa[a]]–).

边差分

理解了点差分,边差分就很好理解了. 边差分的题面 一般是

给你一棵树, 然后给你n多树上路径, 最后问你树上每条边被这些路径覆盖的次数?

令 b[i]刻画的是顶点i和它的父节点的连边被覆盖的次数,那么对于树上路径 u–v, 我们只需要做的事情是

++b[u];
++b[v];
b[a]-=2; // 阻止b[u]、b[v]自增的1往自a--fa[a]以上的边传播

然后最后要统计每条树边被覆盖的次数只需要简单的执行下面的dfs即可

void dfs(int cur)
{
    for(int h = head[cur], to; ~h; h = g[h].nxt)
    {
        to = g[h].to;
        dfs(to);
        b[cur] += b[to];
    }
}

// 调用方法
dfs(root);

至于原因,就不必多说了吧~

至此,已经将树上差分讲解完毕了,四不四很简单? 嘿嘿~ 下面切代码

//#include "stdafx.h"
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
//#define LOCAL
const int maxn = 5e4+5, maxl = 17;
int n,m, cnt, head[maxn], fa[maxn][maxl], dep[maxn], b[maxn], ans;
bool v[maxn];
struct Arc
{
	int from, to, nxt;
}g[maxn << 1];

void addarc(int from, int to)
{
	g[cnt].from = from, g[cnt].to = to, g[cnt].nxt = head[from];
	head[from] = cnt++;
}

void dfs(int cur)
{
	v[cur] = true;
	for (int h = head[cur], to; ~h; h = g[h].nxt)
	{
		to = g[h].to;
		if (v[to])
		{
			continue;
		}
		fa[to][0] =cur;
		dep[to] = dep[cur] + 1;
		dfs(to);
	}
}

int lca(int  x, int y)
{
	if (dep[x] > dep[y])
	{
		swap(x, y);
	}
	for (int j = maxl - 1; ~j; j--)
	{
		if (dep[fa[y][j]] >= dep[x])
		{
			y = fa[y][j];
		}
	}
	if (x == y)
	{
		return x;
	}
	for (int j = maxl - 1; ~j; j--)
	{
		if (fa[x][j] ^ fa[y][j])
		{
			x = fa[x][j];
			y = fa[y][j];
		}
	}
	return fa[x][0];
}

inline void mmax(int &a, int b)
{
	if (a < b)
	{
		a = b;
	}
}

void dfs1(int cur)
{
	v[cur] = true;
	for (int h = head[cur], to; ~h; h = g[h].nxt)
	{
		to = g[h].to;
		if (v[to])
		{
			continue;
		}
		v[to] = true;
		dfs1(to);
		b[cur] += b[to];
	}
	mmax(ans, b[cur]);
}

int main()
{
#ifdef LOCAL
	freopen("d:\\data.in","r",stdin);
//	freopen("d:\\my.out", "w", stdout);
#endif
	memset(head, -1, sizeof(head));
	scanf("%d%d", &n, &m);
	int x, y;
	for (int i = 1; i< n; i++)
	{
		scanf("%d%d", &x, &y);
		addarc(x, y);
		addarc(y, x);
	}
	dep[1] = 1;
	dfs(1);
	for (int j = 1, pa; j < maxl; j++)
	{
		for (int i = 1; i<=n; i++)
		{
			pa = fa[i][j - 1];
			fa[i][j] = fa[pa][j - 1];
		}
	}
	while(m--)
	{
		scanf("%d%d", &x, &y);
		int a = lca(x, y);
		++b[x];
		++b[y];
		--b[a];
		--b[fa[a][0]];
	}
	memset(v, 0, sizeof(v));
	dfs1(1);
	printf("%d", ans);
	return 0;
}

ac情况

所属题目
P3128 [USACO15DEC]Max Flow P
评测状态
Accepted
评测分数
100
编程语言
C++
代码长度
1.87KB
用时
733ms
内存
7.50MB

参考

【1】https://yfsyfs.gitee.io/2020/02/20/sdut-4107-%E9%87%91%E6%B3%BD%E7%9A%84%E5%9C%B0%E5%9B%BE-%E5%B7%AE%E5%88%86%E6%95%B0%E7%BB%84/

【2】https://www.cnblogs.com/ice-wing/p/7709311.html

【3】https://yfsyfs.gitee.io/2020/02/17/%E6%B4%9B%E8%B0%B7-P3384-%E3%80%90%E6%A8%A1%E6%9D%BF%E3%80%91%E9%87%8D%E9%93%BE%E5%89%96%E5%88%86/

【4】https://yfsyfs.gitee.io/2020/02/18/%E6%B4%9B%E8%B0%B7-SP375-QTREE-Query-on-a-tree-%E6%A0%91%E5%89%96/

【5】https://yfsyfs.gitee.io/2020/02/21/%E6%B4%9B%E8%B0%B7-P3379-%E3%80%90%E6%A8%A1%E6%9D%BF%E3%80%91%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88%EF%BC%88LCA%EF%BC%89/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值