【Codeforces】Codeforces Round #660 (Div. 2) D. Captain Flint and Treasure | 树、贪心

96 阅读2分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

【Codeforces】Codeforces Round #660 (Div. 2) D. Captain Flint and Treasure | 贪心

题目链接

Problem - 1388D - Codeforces

题目

image.png

题目大意

给定两个长度为为 nn 的数组 a1,a2,...,ana_1,a_2,...,a_nb1,b2,...,bnb_1,b_2,...,b_n,表示如果取走了 aia_i,将会使得 abi=abi+aia_{b_i}=a_{b_i}+a_i。安排一个取数的顺序,使得取得的所有数之和最大。输出最大的和以及任意最优的取数方案。

思路

bib_i 为节点 ii 的父节点建树。

不考虑权值为负的节点,为了尽可能的多利用正数,我们以类似拓扑排序的方式从叶子节点开始取数,则每个正数对答案的贡献为它到根节点的距离乘以它的权值。该部分我们可以用 BFS 的方式,先将入度为 0 的点加入队列,只要队列非空我们就:

  1. 取出队首元素 d[h]d[h]
  2. 如果队首元素的权值 ad[h]a_{d[h]} 非负,我们认为该节点的权值已经不会再变大了,我们就把它取走加入答案,让 abd[h]=abd[h]+ad[h]a_{b_{d[h]}}=a_{b_{d[h]}}+a_{d[h]}
  3. 如果队首元素的权值 ad[h]a_{d[h]} 为负数,为了不让 abd[h]a_{b_{d[h]}} 变小,我们先不取它。
  4. 我们让节点 bd[h]b_{d[h]} 的入度减一,意为有可能让 bd[h]b_{d[h]} 变大的节点减少了一个。
  5. 如果节点 bd[h]b_{d[h]} 的入度为 0,让其入队。

在操作完之后,我们剩下了一堆权值为负的节点没有取,为了让负权点的影响尽可能小,我们从根节点遍历整棵数,将负权点以 DFS 序取出,则操作完非负权点的数组中的每个负数对答案的贡献为它的权值。

代码

#include <bits/stdc++.h>
using namespace std;
using LL=long long;
const int N=500001;
vector<int> e[N];
int f[N],x,y,z;
int n,m,k;
LL a[N];
int d[N],h,t,cnt[N];
LL ans=0;
vector<int> step;
void dfs(int u)
{
	if (a[u]<0)
	{
		step.push_back(u);
		ans+=a[u];
	}
	for (auto v:e[u])
		dfs(v);
}
int solve()
{
	ans=0;
	step.clear();
	for (int i=1;i<=n;++i)
	{
		cnt[i]=d[i]=0;
		e[i].clear();
	}
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%lld",&a[i]);
	for (int i=1;i<=n;++i)
	{
		scanf("%d",&f[i]);
		if (f[i]==-1) f[i]=0;
		cnt[f[i]]++;
		e[f[i]].push_back(i);
	}
	h=t=0;
	for (int i=1;i<=n;++i) 
		if (!cnt[i]) d[++t]=i;
	while (h!=t)
	{
		h++;
		if (a[d[h]]>=0)
		{
			ans+=a[d[h]];
			step.push_back(d[h]);
		}
		if (!f[d[h]]) continue;
		if (a[d[h]]>=0) a[f[d[h]]]+=a[d[h]];
		if (--cnt[f[d[h]]]==0) d[++t]=f[d[h]];
	}
	dfs(0);
	printf("%lld\n",ans);
	for (auto v:step) printf("%d ",v);
	printf("\n");
}
int main()
{
	solve();
	return 0;
}