『树形DP·换根法』Accumulation Degree

题目描述

有一个树形的水系,由 N-1 条河道和 N 个交叉点组成。

我们可以把交叉点看作树中的节点,编号为 1~N,河道则看作树中的无向边。

每条河道都有一个容量,连接 x 与 y 的河道的容量记为 c(x,y)。

河道中单位时间流过的水量不能超过河道的容量。

有一个节点是整个水系的发源地,可以源源不断地流出水,我们称之为源点。

除了源点之外,树中所有度数为 1 的节点都是入海口,可以吸收无限多的水,我们称之为汇点。

也就是说,水系中的水从源点出发,沿着每条河道,最终流向各个汇点。

在整个水系稳定时,每条河道中的水都以单位时间固定的水量流向固定的方向。

除源点和汇点之外,其余各点不贮存水,也就是流入该点的河道水量之和等于从该点流出的河道水量之和。

整个水系的流量就定义为源点单位时间发出的水量。

在流量不超过河道容量的前提下,求哪个点作为源点时,整个水系的流量最大,输出这个最大值。

O ( N 2 ) O(N^2) 解法

加入这是一棵有根树,我们可以直接利用树形DP来进行求解。

g [ i ] g[i] 表示以 i i 为根的子树中,以 i i 为源点时的最大流量。

若当前根节点为 r o o t root ,子树的根节点为 s o n son

  • s i z e ( s o n ) = 1 size(son)=1 时,有且仅有一条河道,可以直接累加权值。
  • s i z e ( s o n ) > 1 size(son)>1 时,我们需要将 g [ s o n ] g[son] 和边权 v v 进行比较,显然两者取最小值即可。

状态转移方程就是:
g [ r o o t ] = s o n s o n ( r o o t ) { v ( r o o t , s o n ) , s i z e ( s o n ) = 1 m i n ( g [ s o n ] , v ) , s i z e ( s o n ) > 1 g[root]=\sum_{son∈son(root)} \left\{ \begin{aligned} v(root,son),size(son)=1 \\ min(g[son],v),size(son)>1 \\ \end{aligned} \right.

每一个点按照上述方法做一遍树形DP,时间复杂度: O ( n 2 ) O(n^2)

O ( n ) O(n) 解法

由于这是一棵无根树,不同的根会产生不同的答案,故我们可以思考一下如何进行换根。

换根的主要思路就是如何处理根与根的转化。

f [ i ] f[i] 表示以 i i 为根的子树中,答案的最大值。

显然,对于 i i j j 的转移,可以这么考虑:

  • 不变的答案为:在以 1 1 为根的树中,以 j j 为根的子树的答案。即 g [ y ] . g[y].
  • 变化的答案为: j j 上面的答案要变成以j为根的答案。即以 i i 为根的总答案减去在原子树中,以 j j 为根的总答案,在不考虑路径限制的情况下,即为j上面的答案;再与 v ( i , j ) v(i,j) 取最小值即可。我们现在考虑如何求出前者:以 i i 为根的总答案为 f [ i ] f[i] ,以j为根的答案为 m i n ( v ( i , j ) , g [ j ] ) min(v(i,j),g[j]) .现在转移方程就一目了然了。
  • 对于单独的点,直接以边权为答案即可。

有状态转移方程:
f [ i ] = { v ( i , j ) , s i z e ( j ) = 1 g [ j ] + m i n ( v ( i , j ) , f [ i ] m i n ( g [ j ] , v ( i , j ) ) ) , s i z e ( j ) > 1 f[i]=\left\{ \begin{aligned} v(i,j),size(j)=1 \\ g[j]+min(v(i,j),f[i]-min(g[j],v(i,j))),size(j)>1 \end{aligned} \right.

代码如下:

#include <bits/stdc++.h>

using namespace std;
const int N = 200005;

int n, ans, tot;
int in[N], f[N], g[N], Link[N];
struct edge { int y,v,next; } e[N*2];

inline int read(void)
{
    int s = 0, w = 1; char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    while (c >= '0' && c <= '9') s = s*10+c-48, c = getchar();
    return s * w;
} 

void clear(void)
{
	ans = 0, tot = 0;
	memset(in,0,sizeof in);
	memset(Link,0,sizeof Link);
	return;
}

void add(int x,int y,int v)
{
	tot ++;
	e[tot] = edge{y,v,Link[x]};
	Link[x] = tot;
	return;
}

void dfs(int x,int fa)
{
	g[x] = 0;
	for (int i=Link[x];i;i=e[i].next)
	{
		int y = e[i].y;
		int v = e[i].v; 
		if (y == fa) continue;
		dfs(y,x);
		if (in[y] == 1) g[x] += v;
		else g[x] += min(g[y],v);
		//判断流量是否会超出当前容量 
	}
	return;
}

void dp(int x,int fa)
{
	for (int i=Link[x];i;i=e[i].next) 
	{
		int y = e[i].y;
		int v = e[i].v;
		if (y == fa) continue;
		if (in[x] > 1) f[y] = g[y]+min(f[x]-min(g[y],v),v);
		if (in[x] == 1) f[y] = g[y]+v;
		dp(y,x);
	}
	return;
}

void work(void)
{
	clear();
	n = read();
	for (int i=1;i<n;++i)
	{
		int x = read(), y = read(), v = read();
		add(x,y,v), add(y,x,v);
		in[x] ++, in[y] ++;
	}
	dfs(1,0), f[1] = g[1], dp(1,0);
    for (int i=1;i<=n;++i) ans = max(ans,f[i]);
    printf("%d\n", ans);
    return;
}

int main(void)
{
	freopen("degree.in","r",stdin);
	freopen("degree.out","w",stdout);
	int T = read();
	while (T --) work();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/92403897