bzoj5289: [Hnoi2018]排列 排列【贪心+堆】

传送门

解题思路:

首先要看出题目要求的就是一棵树(有环无解),求一个顺序,父亲必须再儿子前选,选第i个点的价值是i*w[i],求最大价值和。
然后这确实是雅礼集训的原题。
先来考虑一个简单的情况:若v是u的儿子且是所有可选点中最小的。那么选了u之后下一个一定会选v。那么我们可以把u与v合并。
这个结论对于一个连通块也是对的,所以块与块之间也可以比较大小,且比较的是平均值。因为考虑连通块 i j 谁先选,即:
w i s i + w j ( s i + s j ) > w j s j + w i ( s i + s j ) w i s i < w j s j
所以我们可以每次找出权值最小的联通快,将其与他的父亲合并。用并查集+堆即可实现。

#include<bits/stdc++.h>
#define double long double
#define ll long long
#define mp make_pair
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=500005;
int n,fa[N],s[N],id[N];
ll w[N],ans;
struct Queue
{
    priority_queue<pair<double,int> >a,b;
    void push(pair<double,int> x){a.push(x);}
    void erase(pair<double,int> x){b.push(x);}
    void pop()
    {
        while(!b.empty()&&a.top()==b.top())a.pop(),b.pop();
        a.pop();
    }
    pair<double,int> top()
    {
        while(!b.empty()&&a.top()==b.top())a.pop(),b.pop();
        return a.top();
    }
}q;
int find(int x){return id[x]==x?x:id[x]=find(id[x]);}
int main()
{
    //freopen("perm.in","r",stdin);
    //freopen("perm.out","w",stdout);
    n=getint();int x,y;
    for(int i=1;i<=n;i++)id[i]=i;
    for(int i=1;i<=n;i++)
    {
        fa[i]=getint();x=find(i),y=find(fa[i]);
        if(x==y){puts("-1");return 0;}
        id[x]=y;
    }
    id[0]=0;
    for(int i=1;i<=n;i++)
    {
        ans+=w[i]=getint(),s[i]=1,id[i]=i;
        q.push(mp(-(double)w[i],i));
    }
    for(int i=1;i<=n;i++)
    {
        x=q.top().second,q.pop(),y=find(fa[x]);
        if(y)q.erase(mp(-(double)w[y]/s[y],y));
        ans+=w[x]*s[y];
        w[y]+=w[x],s[y]+=s[x],id[x]=y;
        if(y)q.push(mp(-(double)w[y]/s[y],y));
    }
    cout<<ans<<'\n';
    return 0;
}


猜你喜欢

转载自blog.csdn.net/cdsszjj/article/details/80243122