一种很经典的贪心套路~
首先考虑合法排列的限制条件,也就是在排列\(p\)中,对于\(\forall i \in [1,N]\) , \(a_i\)要出现在\(i\)的前面。我们连边\((i,a_i)\),表示\(i\)要在\(a_i\)之后出现。那么如果说存在合法的排列,那么最后连成的图一定是一棵树,否则存在环,限制条件冲突。
我们现在知道原图是一棵树了,对于\(\forall i \in [1,N]\) , 令\(i\)的父亲为\(a_i\)。那么对于所有的点,必须要在它的所有祖先选完了之后才能选到它自己。
然后如何去求权值最大值呢?
有一种比较naive的贪心:每一次都选择当前可以选择的点中最小的。
这种贪心策略显然是错的,反例也很好举……
但是这似乎能给我们一些启发:对于当前所有点权中最小的数,它一定会在它的父亲被选完之后立即选。
不妨设当前所有点权中最小点权对应的点权为\(x\),它的父亲权为\(y\),有另一个点权为\(z\),并且满足\(y\)对应的点和\(z\)对应的点现在都可以选
那么现在有两种决策:
1、先选\(y,x\),后选\(z\),权值和为\(y+2x+3z\)
2、先选\(z\),后选\(y,x\),权值和为\(z+2y+3x\)
我们需要取其中更优的,所以我们只需要比较它们的大小。考虑两者同时减去\(x-z\),除以\(2\)
那么第一种方案的权值为\(\frac{x+y}{2}+2z\),第二种方案的权值为\(z + 2\frac{x+y}{2}\)
可以发现之前的两个点\(x,y\)在这个时候可以等价为一个点权为\(\frac{x+y}{2}\)的点
那么可以得到一个贪心:每一次选择一个点权最小的点,把它和它父亲合并,对于一个点的权值为:\(\frac{\text{它所包含的点的点权和}}{\text{它所包含的点的个数}}\)
并查集维护合并过程、在堆中每一次取最小点即可。
最后计算答案时再把合并的点拆开。
#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c)){
if(c == '-')
f = 1;
c = getchar();
}
while(isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return f ? -a : a;
}
const int MAXN = 5e5 + 10;
struct Edge{
int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , pre[MAXN << 1] , fa[MAXN << 1] , ch[MAXN << 1][2] , size[MAXN << 1] , cntEd , cntNode , N , t;
long long pri[MAXN << 1] , ans;
bool vis[MAXN] , mark[MAXN << 1];
struct cmp{
bool operator ()(int a , int b){
return pri[a] * size[b] > pri[b] * size[a];
}
};
priority_queue < int , vector < int > , cmp > q;
int find(int x){
return fa[x] == x ? x : (fa[x] = find(fa[x]));
}
inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
}
bool dfs(int x , int p){
pre[x] = p;
vis[x] = 1;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(Ed[i].end != p)
if(vis[Ed[i].end] || dfs(Ed[i].end , x))
return 1;
return 0;
}
void color(int x){
if(x <= N)
ans += t++ * pri[x];
else{
color(ch[x][0]);
color(ch[x][1]);
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in" , "r" , stdin);
//freopen("out" , "w" , stdout);
#endif
cntNode = N = read();
size[0] = 1;
for(int i = 1 ; i <= N ; ++i){
int a = read();
addEd(a , i);
addEd(i , a);
fa[i] = i;
size[i] = 1;
}
for(int i = 1 ; i <= N ; ++i){
pri[i] = read();
q.push(i);
}
for(int i = 0 ; i <= N ; ++i)
if(!vis[i])
if(dfs(i , -1)){
puts("-1");
return 0;
}
while(!q.empty()){
int t = q.top();
q.pop();
if(mark[t])
continue;
int f = find(pre[t]) , x = ++cntNode;
fa[f] = fa[t] = fa[x] = x;
size[x] = size[f] + size[t];
pri[x] = pri[f] + pri[t];
ch[x][0] = f;
ch[x][1] = t;
pre[x] = pre[f];
mark[t] = mark[f] = 1;
if(pre[x] != -1)
q.push(x);
}
color(cntNode);
cout << ans;
return 0;
}