2020HDU暑假多校第2场

题目地址:http://acm.hdu.edu.cn/search.php?field=problem&key=2020+Multi-University+Training+Contest+2&source=1&searchmode=source

1001:Total Eclipse

题意:给你 n n n个点, m m m条边,点有点权。每次你最多可以选择 k k k个相连的点,使得这些点的点权全部减1,问你最少经过几次操作可以将所有点的点权全部变成0。

思路:首先我们考虑正像的做法,每次选取极大连通块,之后将所有点的点权全部减去连通块里最小的点权,这样这个大的连通块就可以分裂成多个更小的连通块…重复上述操作,直到所有点的点权全部是0。这样我们可以看到时间复杂度特别大,几乎是不能接受的。
所以我们可以考虑将所有点按照点权从大到小排序,之后我们每加入一个点答案就加上
当 前 连 通 块 的 个 数 ∗ ( 当 前 点 的 点 权 − 下 一 个 点 的 点 权 ) 当前连通块的个数*(当前点的点权-下一个点的点权) ()
为什么呢?
我们考虑使用以下的样例来模拟一下算法流程
在这里插入图片描述
首先,我们将点按照点权从大到小排序,得到

6 5 4 3 2 1

1、我们加入6这个点;
2、我们再加入5这个点,我们可以看到此时图中有两个连通块,如下图所示:
在这里插入图片描述
那我们就可以考虑"让6变成5",所需要的代价就是 1 ∗ ( 6 − 5 ) 1*(6-5) 1(65)此时只有一个连通块需要变这样图中我们就可以看成是两块点权为5的连通块;
3、接下来在加入4,如下:
在这里插入图片描述
这样我们就可以看到我们需要将两块点权为5的连通块变成4,需要的代价就是 2 ∗ ( 5 − 4 ) 2*(5-4) 2(54),又6和4这两条边是相连的,那么我们就可以看成是两块点权为4的连通块,如下图:
在这里插入图片描述
4、同理,接下来我们加入3,就相当于有两个点权为4的连通块要变成3,代价为 2 ∗ ( 4 − 3 ) 2*(4-3) 2(43),而且4和三相连,这样就相当于现在剩下的就是两个点权为3的连通块.

依次类推,我们最后就可以得到数个点权为0的连通块,每个过程的累加和便是答案。

代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

#define LL long long
#define pii pair<int,int>
#define sd(x) scanf("%d",&x)
#define slld(x) scanf("%lld",&x)
#define pd(x) printf("%d\n",x)
#define plld(x) printf("%lld\n",x)
#define rep(i,a,b) for(int i = (a) ; i <= (b) ; i++)
#define per(i,a,b) for(int i = (a) ; i >= (b) ; i--)
#define mem(a) memset(a,0,sizeof(a))
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define fast_io ios::sync_with_stdio(false)

const int INF = 1e9 + 10;
const LL mod = 998244353;
const int maxn = 1e5 + 7;

int head[maxn << 2];
struct Edge {
    int to,next;
} edge[maxn << 2];
int tot;

void init() {
    memset(head,-1,sizeof(head));
    tot = 0;
}

void addedge(int u,int v) {
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

int f[maxn];

int Find(int x) {
    if(x == f[x]) return x;
    else return f[x] = Find(f[x]);
}

struct node {
    int val,id;
} a[maxn];

bool cmp(node aa,node bb) {
    return aa.val > bb.val;
}

int vis[maxn];

int main() {
    int T;
    sd(T);
    while(T--) {
        int n,m;
        sd(n),sd(m);
        rep(i,1,n) {
            sd(a[i].val);
            f[i] = a[i].id = i;
        }
        init();
        rep(i,1,m) {
            int u,v;
            sd(u),sd(v);
            addedge(u,v),addedge(v,u);
        }
        sort(a+1,a+n+1,cmp);
        a[n+1].val = 0;
        LL ans = 0;
        LL res = 0;
        mem(vis);
        rep(i,1,n) {
            int u = a[i].id;
            vis[u] = 1;
            res++;
            for(int j = head[u] ; j != -1 ; j = edge[j].next) {
                int v = edge[j].to;
                int t1 = Find(u);
                int t2 = Find(v);
                if(vis[v] && t1 != t2) {
                    f[t1] = t2;
                    res--;
                }
            }
            ans += res * (a[i].val - a[i+1].val) * 1LL;
            // cout << "i = " << i << " ans = " << ans << " " << a[i].val << " " << a[i+1].val << endl;
        }
        plld(ans);
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值