loj 2509. 「AHOI / HNOI2018」排列

22 篇文章 0 订阅
10 篇文章 0 订阅

题意:

这里写图片描述

题解:

感觉很难啊,听说考场乱搞能拿70……
容易转化题意为在森林上移除根节点,每次获得的分数是权值乘上操作次数。
然后就想了各种 随手卡的 sb贪心。
正解挺好的,思路上是每次找到权值最小的点(因为这样就保证当父亲被选了后立刻就选到他),将他和父亲缩成一个点,并且计算这个点在这个联通块的贡献,直到所有点缩成一个点。
关键就是怎么确定新点的权值。
答案就是平均值。
例如:
现在有两个联通块,权值分别是 X,Y X , Y ,大小是 n,m n , m 。忽略掉共同对答案的贡献,那么就只剩下这两个联通块相互的影响,分别是 nY,mX n Y , m X ,移项可得 Ym,Xn Y m , X n
code:

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define LL long long
using namespace std;
typedef pair<long double,LL> pi;
LL n,a[500005],fa[500005],f[500005],w[500005],size[500005];
LL findfa(LL x) {return fa[x]==x?x:fa[x]=findfa(fa[x]);}
struct Queue{
    priority_queue<pi> a,b;
    void push(pi x) {a.push(x);}
    void pop()
    {
        while(!b.empty()&&a.top()==b.top()) a.pop(),b.pop();
        a.pop();
    }
    void erase(pi x) {b.push(x);}
    pi top()
    {
        while(!b.empty()&&a.top()==b.top()) a.pop(),b.pop();
        return a.top();
    }
}q;
int main()
{
    scanf("%lld",&n);
    for(LL i=0;i<=n;i++) fa[i]=i;
    for(LL i=1;i<=n;i++)
    {
        scanf("%lld",&f[i]);
        if(fa[findfa(i)]==fa[findfa(f[i])]) return puts("-1"),0;
        fa[findfa(i)]=findfa(f[i]);
    }
    LL ans=0;
    for(LL i=1;i<=n;i++) scanf("%lld",&w[i]),ans+=w[i];
    for(LL i=1;i<=n;i++) fa[i]=i,size[i]=1,q.push(make_pair(-(long double)w[i],i));
    for(LL i=1;i<=n;i++)
    {
        LL x=q.top().second,tx=findfa(f[x]);q.pop();
        if(tx) q.erase(make_pair(-(long double)w[tx]/size[tx],tx));
        ans+=size[tx]*w[x];
        w[tx]+=w[x];size[tx]+=size[x];fa[x]=tx;
        if(tx) q.push(make_pair(-(long double)w[tx]/size[tx],tx));
    }
    printf("%lld",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值