本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
【Codeforces】Codeforces Round #660 (Div. 2) D. Captain Flint and Treasure | 贪心
题目链接
题目
题目大意
给定两个长度为为 的数组 和 ,表示如果取走了 ,将会使得 。安排一个取数的顺序,使得取得的所有数之和最大。输出最大的和以及任意最优的取数方案。
思路
以 为节点 的父节点建树。
不考虑权值为负的节点,为了尽可能的多利用正数,我们以类似拓扑排序的方式从叶子节点开始取数,则每个正数对答案的贡献为它到根节点的距离乘以它的权值。该部分我们可以用 BFS 的方式,先将入度为 0 的点加入队列,只要队列非空我们就:
- 取出队首元素
- 如果队首元素的权值 非负,我们认为该节点的权值已经不会再变大了,我们就把它取走加入答案,让 。
- 如果队首元素的权值 为负数,为了不让 变小,我们先不取它。
- 我们让节点 的入度减一,意为有可能让 变大的节点减少了一个。
- 如果节点 的入度为 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;
}