2020年HDU多校第二场 1001 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
",ans);
}
}