Luogu P5521 [yLOI2019] 梅深不见冬

题目背景

风,吹起梅岭的深冬;霜,如惊涛一样汹涌;
雪,飘落后把所有烧成空,像这场,捕捉不到的梦。
醒来时已是多年之久,宫门铜环才长了铁锈,也开始生出离愁。

——银临《梅深不见冬》

题目描述

扶苏从深冬的梅岭走出,来到了一棵有 n n n 个节点的有根树上。

如果你不知道什么是树,可以认为树是一个边数恰好比节点个数少一的简单无向连通图。

如果我们规定 x x x 是树 T T T 的根,那么定义任意一个节点 y y y 到根的路径就是从 y y y 出发不重复经过节点到达 x x x 所经过的所经过的点构成的点集。可以证明这样的点集有且仅有一个。

定义一个节点 u u u 是节点 v v v 的孩子,当且仅当 u u u v v v 相连且 u u u 不在 v v v 到根的路径中。如果 u u u v v v 的孩子,那么定义 v v v u u u 的家长节点。

如果我是 @_rqy 那种毒瘤神仙的话,可能会问你每个节点的孩子数不超过 k k k n n n 个节点的带标号无根树一共有多少个,可惜这个问题我也不会,所以我不会问你这么毒瘤的问题。

扶苏从这棵 n n n 个节点的树的 1 1 1 号节点出发,沿着树上的边行走。当然我们规定 1 1 1 号节点是这棵树的根。他所行走的规定是:当扶苏在节点 u u u 时,扶苏要么在 u u u 的孩子中选择一个没有到达过的节点 v v v 并行走到 v v v,要么选择回到 u u u 的家长节点。

现在给每个节点一个权值 w w w,其中 i i i 号节点的权值为 w i w_i wi。他想给这棵树的某个节点放上从梅岭带出的梅花。我们规定扶苏能在节点 u u u 放上梅花当且仅当满足如下条件:

扶苏当前在节点 u u u

对于 u u u 的所有孩子 v v v,节点 v v v 被放上了 w v w_v wv 朵梅花。

同时,扶苏可以在任意时刻收回任意节点上的梅花,在收回梅花时不需要走到对应节点。

现在扶苏想问问你,对于每个节点,如果他想在 i i i 号节点上放 w i w_i wi 朵梅花,那么他最少要从梅岭带出多少朵梅花。

输入格式

每个输入文件中都有且仅有一组测试数据。

数据的第一行是一个整数 n n n 代表树的节点个数。

第二行有 n − 1 n-1 n1 个用空格隔开的整数,第 i i i 个整数 p i p_i pi 代表第 i + 1 i+1 i+1 号节点的家长节点编号。

第三行有 n n n 个用空格隔开的整数,第 i i i 个整数代表 w i w_i wi

输出格式

输出一行 n n n 个用空格隔开的整数,第 i i i 个整数代表想在 i i i 号节点上放 w i w_i wi 朵梅花需要准备的梅花个数。

样例 #1

样例输入 #1

3 
1 2 
1 1 1

样例输出 #1

2 2 1

样例 #2

样例输入 #2

3
1 1
1 1 1

样例输出 #2

3 1 1

样例 #3

样例输入 #3

6
1 1 2 3 4
3 14 1 5 12 15

样例输出 #3

21 20 13 20 12 15

提示

输入输出样例 1 解释

qwq

样例 1 的输入如上图,每个节点都需要放 1 1 1 一朵梅花。

如果在 1 号节点放梅花,则从一号点运动到 2 号点,然后运动到 3 号点,在 3 号点上放一朵梅花,返回 2 号点,在 2 号点上放一朵梅花,同时收回三号点的梅花,然后返回 1 号点,将从 3 号点收回的梅花放到 1 号点即可。一共需要两朵梅花。

在 2、3 号节点放梅花的方案类似。

输入输出样例 3 解释

qwq

样例 3 的输入如左图。

先从 1 号节点运动至 3 号节点,再运动至 5 号节点,在 5 号节点上放置 12 12 12 朵梅花,然后返回 3 号节点,在 3 号节点上放置 1 1 1 朵梅花,收回五号节点的 12 12 12 朵梅花,返回 1 号节点。

然后运动到 2 号节点,通过 4 号节点运动到 6 号节点,放下 15 15 15 朵梅花,返回 4 号节点放下 5 5 5 朵梅花,此时树上有的梅花数为 5 + 15 + 1 = 21 5 + 15 + 1 = 21 5+15+1=21,分别在 4 号、6 号和 3 号节点上。然后收回 6 号节点的梅花,返回 2 号节点,放下 14 14 14 朵梅花,收回 4 号节点的,返回 1 号节点,在 1 号节点上放置 3 3 3 朵梅花,即可达到在 1 号节点上放梅花的目的。

可以验证最大花费为 21 21 21。其他节点的答案同理。

请注意,其他节点的答案不一定是按照该节点的运动路径行走得到的。


数据规模与约定

测试点编号$n = $测试点编号$n = $
1 1 1 111 100003 100003 100003
2 8 8 812 100003 100003 100003
3 8 8 813 100003 100003 100003
4 8 8 814 100003 100003 100003
5 8 8 815 100004 100004 100004
6 100000 100000 10000016 100004 100004 100004
7 100000 100000 10000017 100004 100004 100004
8 100002 100002 10000218 100004 100004 100004
9 100002 100002 10000219 100004 100004 100004
10 100002 100002 10000220 100004 100004 100004
  • 对于测试点 5、6,满足特殊性质:每个节点的孩子结点个数不超过 2 2 2
  • 对于测试点 8 到测试点 10,满足特殊性质:每个节点的孩子节点个数不超过 5 5 5
  • 对于测试点 11 到测试点 14,满足特殊性质:任意一个节点到根的路径上的点数不超过 3 3 3,也即树高不超过 3 3 3
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 5 + 4 ,   1 ≤ p i ≤ i ,   1 ≤ w i ≤ 1000 1 \leq n \leq 10^5 + 4,~1 \leq p_i \leq i,~1 \leq w_i \leq 1000 1n105+4, 1pii, 1wi1000

提示

  • n n n 的末位数字可以帮助你快速的判断测试点所具有的的特殊性质。

树形dp、贪心 - AC

题面有点绕人,看看样例就懂了。

显然,想在节点u上放梅花,只与以点u为根的子树有关,考虑树形dp。
定义 d p ( u ) dp(u) dp(u)是u的答案,也就是在u上放梅花的过程中,点u为根的子树上梅花总个数达到过的最大值。

一开始以为直接模拟即可,后来发现,考虑u的答案时,放了儿子v1后,走在儿子v2上时,v1上依然有梅花,这使得问题不仅仅是在v2为根的子树上了。即,走子节点的顺序重要。
已知顺序, d p ( u ) dp(u) dp(u)就在这些中取最大值: d p ( v 1 ) , d p ( v 2 ) + w v 1 , d p ( v 3 ) + w v 1 + w v 2 , ⋯   , w u + ∑ w v dp(v_1),dp(v_2)+w_{v_1},dp(v_3)+w_{v_1}+w_{v_2},\cdots,w_u+\sum w_v dp(v1),dp(v2)+wv1,dp(v3)+wv1+wv2,,wu+wv.
最后一项是定值。前面的,最大值最小化,考虑交换调整法找出排序规律(一开始以为直接按w从小到大排序就行了,后来发现还有dp要考虑)。
1要排在2前面,则 m a x { d p ( 1 ) , d p ( 2 ) + w 1 } < m a x { d p ( 2 ) , d p ( 1 ) + w 2 } max\{dp(1),dp(2)+w_1\}<max\{dp(2),dp(1)+w_2\} max{dp(1),dp(2)+w1}<max{dp(2),dp(1)+w2},这让我想到国王游戏。w是正整数,则 d p ( 2 ) + w 1 > d p ( 2 ) dp(2)+w_1>dp(2) dp(2)+w1>dp(2),则 d p ( 2 ) + w 1 < d p ( 1 ) + w 2 dp(2)+w_1<dp(1)+w_2 dp(2)+w1<dp(1)+w2,得 d p ( 1 ) − w 1 > d p ( 2 ) − w 2 dp(1)-w_1>dp(2)-w_2 dp(1)w1>dp(2)w2,OK了。

int n, p[MAXN], w[MAXN], dp[MAXN];
vector<int> g[MAXN];

void dfs(int u) {
	for (int v : g[u]) dfs(v);
	sort(g[u].begin(), g[u].end(), [&](const int& x, const int& y) {
		return dp[x] - w[x] > dp[y] - w[y]; 
	} );
	int s = 0;
	for (int v : g[u]) ckmax(dp[u], dp[v] + s), s += w[v];
	ckmax(dp[u], w[u] + s);
}

int main() {
	scanf("%d", &n);
	for (int i = 2; i <= n; ++i) {
		scanf("%d", p + i);
		g[p[i]].push_back(i);
	}
	for (int i = 1; i <= n; ++i) {
		scanf("%d", w + i);
	}
	
	dfs(1);
	for (int i = 1; i <= n; ++i) {
		printf("%d ", dp[i]);
	} printf("\n");
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值