题意:
有两个长度为 n n n的数组 a a a和 b b b。最初, a n s = 0 ans=0 ans=0,并定义了以下操作:
选择位置
i
(
1
≤
i
≤
n
)
i(1≤i≤n)
i(1≤i≤n):
a
n
s
+
=
a
i
ans+=a_i
ans+=ai;
如果
b
i
≠
−
1
b_i≠-1
bi=−1,则
a
b
i
+
=
a
i
a_{b_i}+=a_i
abi+=ai。
对每个 i ( 1 ≤ i ≤ n ) i(1≤i≤n) i(1≤i≤n)进行一次操作,输出最大 a n s ans ans,以及方案。
保证对于任意 i ( 1 ≤ i ≤ n ) i(1 \le i \le n) i(1≤i≤n), b i , b b i , b b b i , … b_i, b_{b_i}, b_{b_{b_i}}, \ldots bi,bbi,bbbi,…不会形成循环。
题解:
在纸上写一下 a a a,当 b i ≠ − 1 b_i\ne -1 bi=−1时,将 i i i与 b i b_i bi连接一条边。由于没有循环,我们会发现, a a a数组形成了一个森林。也就是,我们每选择一个 i i i, a n s = a n s + a i ans=ans+a_i ans=ans+ai后, a i a_i ai的值向上传递一次,也就是 i i i的父亲节点 j j j, a j = a j + a i a_j=a_j+a_i aj=aj+ai。
对于每一棵树,采用贪心的思想。当 a i > 0 a_i>0 ai>0,让它多做贡献;当 a i < 0 a_i<0 ai<0,让它少做贡献。那么也就是,正数先选择深度大的,负数先选择深度小的。两遍dfs就完事。因为选择正数的时候父节点还没选,可能父节点原来是负的,加上正的子节点后就变成正的了。所以先从底往上dfs选正的节点,再从顶往下dfs选负的节点。
并查集把 a a a分成森林就不多说了。
AC代码:
#include <bits/stdc++.h>
#define pb push_back
#define fir first
#define sec second
#define ms(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define sp system("pause")
#define multi int t;cin>>t;while(t--)
using namespace std;
typedef long long ll;
typedef double db;
const int N=2e5+5;
const int mod=10007;
const db pi=acos(-1.0);
vector<int>tr[N];
ll a[N],ans=0;
queue<int>q;
int num[N],vis[N],b[N],fa[N];
void dfs1(int u,int fa){
for(int v:tr[u]){
if(v==fa) continue;
dfs1(v,u);
if(a[v]>0) a[u]+=a[v];
}
if(a[u]>0){
ans+=a[u];
q.push(u);
vis[u]=1;
}
}
void dfs2(int u,int fa){
if(!vis[u]){
ans+=a[u];
q.push(u);
vis[u]=1;
}
for(int v:tr[u]){
if(v==fa) continue;
dfs2(v,u);
}
}
int get(int x){
return x==fa[x]?x:fa[x]=get(fa[x]);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("D:\\work\\data.in","r",stdin);
#endif
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
fa[i]=i;
}
for(int i=1;i<=n;i++){
cin>>b[i];
if(b[i]!=-1){
tr[b[i]].pb(i);
fa[i]=b[i];
}
}
for(int i=1;i<=n;i++){
if(get(i)==i){
dfs1(i,-1);
dfs2(i,-1);
}
}
cout<<ans<<endl;
while(!q.empty()){
cout<<q.front()<<" ";
q.pop();
}
}