[2020HDU多校]Total Eclipse HDU 6763

2020HDU多校训练 专栏收录该内容
1 篇文章 0 订阅

[2020HDU多校]Total Eclipse HDU 6763

题意:

给定一堆点, 点有权值, 每一次操作可以选择一些点, 要求这些点是相互连通的, 选完之后每一个点的权值减少1, 要求点的权值最终都变成0. 即**变成0的点不能再被选择(否则就减一出现负值了).**问操作最少需要多少次.

直观分析: 这里很抽象,看不懂也没事,反正解法和这个有很大的区别.

从小到大减小: 那么我可以选择尽量大的范围,即刚开始每一个有权值的联通的点都选中, 然后减去选中的当中最小的权值(因为每一次都对这些点减去1最终效果也就是全部选中并且减去其中的最小的权值).这样之后点图就会出现0权值的点.这些0权值的点不能够再次被选择,那么就是相当于图被这几个0权值的点分割成几个联通分量,那么就需要对于联通分量做一样的操作.

从大权值到小权值减: 这个理解应该还是不难的.就相当于把大的权值想办法减小,和周围的其他的初始权值较小的点权值靠近,最终达到的目的就是让大的权值的点逐渐减小,使得全图的权值一样.那么就可以对全图每一个点都选中进行操作.

解题策略

相当于每次对整个图中权值最大的点向下降低,降低成为与邻居中某一个权值和它一样大的时候把他们"合"成一体,就是他们的权值都一样了,并且是有边直连的,那么下次其中一个点被选中做减少的时候另一个点肯定被选中也参与这个操作.(因为如果不是同时都选,只降低其中一个,下次又要花费操作来降低这个点.)

补充上面,对于全图的最大的点降权值,最先和它邻居的权值一致一定是周围的邻居中权值最大的那一个,因为最大的点每一次也只能减少1,所以一定会最先和最大的邻居节点合并.至于为什么这个邻居节点不会比自己大呢.因为上面说了,这个点是当前的全图的最大的权值的点.边上最大的邻居点顶多就是自己同权值的点.

那么是不是这就要用连通图联通分量等等来做了呢? 不用

解题思路是:先对每一个边排序:排序方案是每一条边连这两个点, 两个点有各自的权值,排序方案是按照这两个权值中小的那个权值作为排序的依据.索引到每一个边的作用是让这条边的大的权值点合并到小的权值的点上去.那么如果当前的合并方案不是最合理的怎么办:比如如果是5权值的节点,邻居有4,2,1权值的节点.如果当前边是5和2的连边,但是最合理的是先让5链接到4权值再进一步降低,那这样不就出错了吗??.回答是不会的, 因为边的排序方案是让边的两个权值中较小的那个权值降序排列,所以5-4的边一定会比5-2的边更早被遍历到.所以按照边的出现的顺序正常下降是不会有问题的.

对于每一个边,让大的点的权值改为较小的点的权值,这次记操作次数为改变的差值贡献给答案.至于怎么实现这次改动让之后的遍历到的边也能更新当前的权值:用并查集.因为更新权值的更新后一定对应着某一个点的初始权值,那么我就把需要降低到哪一个点,就把当前大的点的父亲指向那个相对小的点的编号,所以在用边去找到点的权值的时候必须要做的就是先去找到两个点对应的父亲节点是谁,父亲节点的含义是父亲节点的权值没有发生过变化,而当前的节点权值和父亲节点的权值一样(已经降低到父亲节点一样的权值了).

说了一大堆,不知道有没有表达清楚.因为这个题从懵逼到理解中间想了非常非常多的细节.

#include<bits\stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 6e5+60;

ll saveori[maxn];

struct edge{
    int minid,maxid;
    ll minv,maxv;
    bool operator<(const edge& e)const{
        return minv > e.minv;
    }
}save[maxn];

int fa[maxn];

inline int Fa(int x){
    if(fa[x] != x) fa[x] = Fa(fa[x]);
    return fa[x];
}

void solve(){
    int n,m;
    scanf("%d %d",&n, &m);
    for(int i=1;i<=n;i++){
        scanf("%d",&saveori[i]);
        fa[i] = i;
    }

    int s, d;
    for(int i=0;i<m;i++){
        scanf("%d %d",&s, &d);
        if(saveori[s]<saveori[d])
            swap(s,d);
        save[i].minid = d;
        save[i].maxid = s;
        save[i].minv = saveori[d];
        save[i].maxv = saveori[s];
    }

    sort(save, save+m);
    ll ans = 0;
    for(int i=0;i<m;i++){
        edge nowe = save[i];
        int MINid = Fa(nowe.minid);
        int MAXid = Fa(nowe.maxid);
        ans+=saveori[MAXid] - saveori[MINid];
        saveori[MAXid] = saveori[MINid];
        fa[MAXid] = MINid;
    }
    for(int i=1;i<=n;i++){
        if(fa[i]==i)ans+=saveori[i];
    }
    printf("%lld\n",ans);

}

int main(){
    // freopen("in.txt", "r", stdin);
    int T;
    cin>>T;
    while(T--){
        solve();
    }
}

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页

打赏作者

Π鱼星先生

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值