2020年HDU多校第二场 1001 Total Eclipse(思维)

2020年HDU多校第二场 1001 Total Eclipse(思维)

Total Eclipse

题意:每次选一个连通块使里面所有点的值减一,当某个点减为零时其点连的所有边都会删去,求多少次操作使所有点变成0。

题解:这个题感觉还是比较难想的,比赛时想了2个多小时也没想到;

先给个样例:

1

3 2

1 2 3

3 1

1 2

可以将图画成柱形图(关系复杂的图不好画,这里是方便理解):

显然答案为4,高度为一,切1刀,剩一个2与1不连通一共切3刀,共四次,那么我们是不是也可以反过来想,我将3号最上面切一刀与2等高,将3与2分别再切一刀与1等高,此时1将3与2连通,再一起切一刀。那么反过来想的好处是什么呢,正推是原本连通的点将不连通,要花o(n)时间重新建图,而反推,连通的点继续连通,新加进来的点可能将更多点连通,具有单调性,即可以优化大量时间,怎么记录连通关系啥的就不多说了,并查集搞一搞,cnt记录一下连通块数量既可

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
ll to,t,n,m,u,v,fa[100007],vis[100007],cnt,h,ans,lin;
struct madoka{
    ll p;
    ll h;
}a[100007],now;
ll fin(ll p){
    if(p==fa[p])return p;
    else{
        return fa[p]=fin(fa[p]);
    }
}
bool cmp(madoka a1,madoka a2){
    return a1.h>a2.h;
}
vector<int>ho[100007];
void init(){
    for(int i=1;i<=n;i++)ho[i].clear(),vis[i]=0;
}
int main(){
    scanf("%lld",&t);
    while(t--){
        scanf("%lld%lld",&n,&m);
        init();
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i].h);
            a[i].p=i;
            fa[i]=i;
        }
        for(int i=1;i<=m;i++){
            scanf("%lld%lld",&u,&v);
            ho[u].push_back(v);
            ho[v].push_back(u);
        }
        sort(a+1,a+1+n,cmp);
        h=a[1].h;
        vis[a[1].p]=1;
        cnt=1;
        ans=0;
        for(int i=2;i<=n;i++){
            now=a[i];
            lin=(h-a[i].h);
            ans=(ans+lin*cnt);
            for(int j=0;j<ho[now.p].size();j++){
                to=ho[now.p][j];
                if(fin(now.p)!=fin(to)&&vis[to]){
                    cnt--;
                    fa[fin(now.p)]=fin(to);
                }
            }
            vis[now.p]=1;
            h=a[i].h;
            cnt++;
        }
        ans=(ans+h*cnt);
        printf("%lld\n",ans);
    }
}
posted @ 2020-07-24 16:21  ccsu_madoka  阅读(190)  评论(0编辑  收藏  举报