『树形DP·换根』The Crazy Tree(毒瘤)

题目描述

给定一棵 n n 个结点的树,结点编号为 1 n 1-n i i 号结点的权重记为 w i wi (每个点的权值各不相同)。我们定义一个“疯子树”为: 1. 是一个联通子图。 2. 我们将子图内的点按照权重从小到大排序后序列为 b 1 , b 2 , , b m b1,b2,…,bm ,对于任意的 i ( i < m ) i(i<m) , b i bi b i + 1 bi+1 。最短路径(不含 b i bi b i + 1 bi+1 )上的结点的权值都小于等于 w b i w_{bi} 。输出包含结点最多的“疯子树”的结点数。

题解

首先,树中两点的路径唯一,不需要考虑最短路。

其次,我们考虑一下疯子书这个树有什么特殊的性质:

  • 我们发现,每一棵疯子树中任意两点间的权值都小于这两个点。如果是一条链的话,必然是连续的不断并且一次递减;如果是一棵树的话,这棵树和每一个子树都满足根节点为最小点的树一定是疯子树。

显然,以不同的节点为根会产生不同的答案,由于题目中说的是无根树我们可以采用换根的方法来进行处理。

我们设 g [ x ] g[x] 表示以 x x 为根的子树中,节点最多的疯子树。根据疯子树的性质,我们可以得到:
g [ x ] = 1 + y s o n ( x ) , w y w x g [ y ] g[x]=1+\sum_{y∈son(x),w_y≥w_x} g[y]
同时,我们第二次进行操作时,设 f [ i ] f[i] 表示以 i i 为根最后的总答案。同样可以得到:
f [ x ] = g [ x ] + f [ f a ] ( w h e n   w x w f a ) f[x]=g[x]+f[fa](when\ w_x≥w_{fa})
此时由于满足条件 w x w f a w_x≥w_{fa} , g [ x ] g[x] 中不可能包含父节点 f a fa

时间复杂度: O ( N ) O(N) .

代码如下:

#include <bits/stdc++.h> 

using namespace std;
const int N = 200010;

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;
} 

int n;
int f[N], g[N], w[N];
vector <int> a[N];

void dfs(int x,int fa)
{
	g[x] = 1;
	for (int i=0;i<a[x].size();++i)
	{
		int y = a[x][i];
		if (y == fa) continue;
		dfs(y,x);
		if (w[y] >= w[x]) g[x] += g[y];
	}
	f[x] = g[x];
	return;
}

void dp(int x,int fa)
{
	for (int i=0;i<a[x].size();++i)
	{
		int y = a[x][i];
		if (y == fa) continue;
		if (w[y] <= w[x]) f[y] += f[x];
		dp(y,x);
	}
	return;
}

int main(void)
{
	freopen("crazy.in","r",stdin);
	freopen("crazy.out","w",stdout);
	while (cin >> n)
	{
		for (int i=1;i<=n;++i) w[i] = read();
		for (int i=0;i<N;++i) a[i].clear();
		for (int i=1;i<n;++i)
		{
			int x = read(), y = read();
			a[x].push_back(y);
			a[y].push_back(x);
		}
		dfs(1,0), dp(1,0);
		int ans = 0;
		for (int i=1;i<=n;++i) ans = max(ans,f[i]);
		cout << ans << endl;
	}
	return 0;
}

因此我们便可以放心DP喽~

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/92636556
今日推荐