bzoj5289 [Hnoi2018]排列(贪心+树+set)

首先把限制翻译一下就是考虑p这个排列, ai a i 必须出现在i前面,连边 ai a i ->i。发现约束关系是一棵以0为根的树。(有环就无解)
现在的问题就变为,给定一棵树,每个点有点权,你要给每个点再分配一个不同的 pi p i ,需要满足 pi>pfa[i] p i > p f a [ i ] ,使得 ipiai ∑ i p i a i 最大。
有一种神奇的贪心做法。
先来考虑一个简单的情况:若w[i]< w[fa[i]]且是w最小的儿子。那么选了fa[i]之后下一个一定会选i。那么我们可以把i与fa[i]合并。
这个结论对于一个连通块也是对的,所以块与块之间也可以比较大小,即比较sumw/sz。所以我们可以每次找出权值最小的联通快,将其与他的父亲合并。(考虑两个连通块,如果i在j前面,那么一定满足 wi+wjsi>wj+wisj w i + w j ∗ s i > w j + w i ∗ s j

可以用并查集+set实现。
复杂度 O(nlogn) O ( n l o g n )

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 6e14
#define N 500010
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(T==S){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,pa[N],fa[N],sz[N];
ll w[N],ans=0;
struct cmp{
    bool operator()(int a,int b){return w[a]*sz[b]==w[b]*sz[a]?a<b:w[a]*sz[b]<w[b]*sz[a];}
};
set<int,cmp>st;
inline int find(int x){return x==pa[x]?x:pa[x]=find(pa[x]);}
int main(){
//  freopen("a.in","r",stdin);
    n=read();for(int i=1;i<=n;++i) pa[i]=i;
    for(int i=1;i<=n;++i){
        int x=read();fa[i]=x;int xx=find(x),yy=find(i);
        if(xx==yy){puts("-1");return 0;}pa[xx]=yy;
    }for(int i=1;i<=n;++i) w[i]=read(),sz[i]=1,st.insert(i),pa[i]=i;w[0]=inf;sz[0]=1;st.insert(0);pa[0]=0;
    while(!st.empty()){
        int x=*st.begin();if(!x) break;st.erase(x);
        int y=find(fa[x]);st.erase(y);ans+=w[x]*sz[y];
        pa[x]=y;w[y]+=w[x];sz[y]+=sz[x];st.insert(y);
    }printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值