Description
给定 个整数 以及 个整数 。称 的一个排列 为一个合法排列当且仅当该排列满足:对于任意的 和任意的 ,如果 ,那么 。定义一个合法排列的权值为 。
你需要求出所有合法排列中的最大权值。如果不存在,输出 -1
。
Solution
这道题好神啊。
首先给出的这个限制不是很直观,我们转化一下来方便理解。
现在有一个空序列 p[]
,我们往这里面依次加 ,要求加完以后成了一个排列。
当加到第 个的时候,此时 确定了, 确定了,如果我们还没有将 放入序列中,就意味着必然存在一个 ,使得 。这是不合法的。
得出结论:最终序列中 一定要放在 前面。这与序列是否合法互为充要条件。
转化了限制以后题目变得好做了一些。我们让这个限制更加直观一点,把限制上图。
令 是 的父亲,则原图每个点只有一个入度,要么是一个有向无环树,要么有环。有环自然就是无解,而在有向无环树中,每一个拓扑序都是一个合法排列。
所以拓扑序的第 位(假设为 )的权值是 。也就是在树上从根开始遍历整棵树,每棵树的点权为 ,第一次经过的时刻为 ,那么贡献就是
接下来我们需要让贡献最大化。考虑目前的 最小值的点 ,若 的父亲被选,那么 一定是紧接着被选,于是我们可以合并这两个点,并重新赋贡献,这个贪心较显然。扩展这个贪心至整棵树的关键之处在于如何重新赋贡献。考虑某个时刻,点 下挂了两个点 和 。 和 目前均代表一个序列( p
序列的一部分 ),我们是先放 还是先放 呢?在贪心中,抽象化两个物品,并直接比较两种不同方法的贡献是一种不错的方法。令 和 分别是 和 代表的序列的 之和, 和 分别是 和 代表的序列的长度 ,那么两种情况贡献的差值为:
于是维护好各自的 和 就可以利用堆来实现贪心了。
复杂度 。
Code
const int N = 5e5 + 5;
using namespace std;
int n, tot;
int f[N], fa[N], sz[N], vis[N];
long long v[N];
vector<int> c[N];
struct node{
int u, sz;
long long sv;
bool operator < (const node &b) const{
return 1ll * sz * b.sv < 1ll * sv * b.sz;
}
};
void dfs(int p){
++tot, vis[p] = 1;
for(auto u:c[p])
if(!vis[u]) dfs(u);
}
int Find(int x){
return x == f[x] ? x : f[x] = Find(f[x]);
}
int main(){
r(n);
for(int i = 1; i <= n; ++i)
r(fa[i]), c[fa[i]].push_back(i);
for(int i = 1; i <= n; ++i)
r(v[i]);
dfs(0);
if(tot <= n) return puts("-1"),0;
for(int i = 0; i <= n; ++i)
f[i] = i, sz[i] = 1;
priority_queue<node>q;
for(int i = 1; i <= n; ++i)
q.push((node){i, 1, v[i]});
long long ans = 0;
while(!q.empty()){
node now = q.top(); q.pop();
int u = Find(now.u);
if(sz[u] != now.sz) continue;
int anc = f[u] = Find(fa[u]);
ans += 1ll * v[u] * sz[anc];
sz[anc] += sz[u], v[anc] += v[u];
if(anc) q.push((node){anc, sz[anc], v[anc]});
}
w(ans);
return 0;
}