[HNOI2018] 排列

Description

给定 nn 个整数 a1,a2,,ana_1,a_2,\cdots,a_n 以及 nn 个整数 w1,w2,,wnw_1,w_2,\cdots,w_n 。称 a1,a2,,ana_1,a_2,\cdots,a_n 的一个排列 ap1,ap2,,apna_{p_1},a_{p_2},\cdots,a_{p_n} 为一个合法排列当且仅当该排列满足:对于任意的 kk 和任意的 jj ,如果 jkj\leq k ,那么 apjpka_{p_j}\ne p_k 。定义一个合法排列的权值为 wp1+2wp2++nwpnw_{p_1}+2w_{p_2}+\cdots + nw_{p_n}
你需要求出所有合法排列中的最大权值。如果不存在,输出 -1
n5×105,ai[0,n],wi1.5×1013n\leq 5\times 10^5,a_i\in[0,n],\sum w_i\leq 1.5\times 10^{13}

Solution

这道题好神啊。
首先给出的这个限制不是很直观,我们转化一下来方便理解。
现在有一个空序列 p[] ,我们往这里面依次加 1n1\sim n ,要求加完以后成了一个排列。
当加到第 ii 个的时候,此时 pip_i 确定了,apia_{p_i} 确定了,如果我们还没有将 apia_{p_i} 放入序列中,就意味着必然存在一个 kk ,使得 api=pkika_{p_i} = p_k \cap i\leq k 。这是不合法的。
得出结论:最终序列中 aia_i 一定要放在 ii 前面。这与序列是否合法互为充要条件。
转化了限制以后题目变得好做了一些。我们让这个限制更加直观一点,把限制上图。
aia_iii 的父亲,则原图每个点只有一个入度,要么是一个有向无环树,要么有环。有环自然就是无解,而在有向无环树中,每一个拓扑序都是一个合法排列。

所以拓扑序的第 TT 位(假设为 xx)的权值是 T×wpT=T×wxT\times w_{p_T}=T\times w_x 。也就是在树上从根开始遍历整棵树,每棵树的点权为 wiw_i ,第一次经过的时刻为 TiT_i ,那么贡献就是

i=1nwiTi\sum_{i=1}^{n}w_iT_i

接下来我们需要让贡献最大化。考虑目前的 wuw_u 最小值的点 uu ,若 uu 的父亲被选,那么 uu 一定是紧接着被选,于是我们可以合并这两个点,并重新赋贡献,这个贪心较显然。扩展这个贪心至整棵树的关键之处在于如何重新赋贡献。考虑某个时刻,点 pp 下挂了两个点 uuvvuuvv 目前均代表一个序列( p 序列的一部分 ),我们是先放 uu 还是先放 vv 呢?在贪心中,抽象化两个物品,并直接比较两种不同方法的贡献是一种不错的方法。令 Wu\mathcal{W}_uWv\mathcal{W}_v 分别是 uuvv 代表的序列的 ww 之和,Su\mathcal{S}_uSv\mathcal{S}_v 分别是 uuvv 代表的序列的长度 ,那么两种情况贡献的差值为:

wuvwvu=Su×WvSv×Wuw_{u\rightarrow v} - w_{v\rightarrow u} = \mathcal{S}_u\times \mathcal{W}_v - \mathcal{S}_v\times \mathcal{W}_u

于是维护好各自的 S\mathcal{S}W\mathcal{W} 就可以利用堆来实现贪心了。

复杂度 O(nlog2n)\mathcal{O}(n\log_2 n)

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;
}