A - Total Eclipse
链接:http://acm.hdu.edu.cn/showproblem.php?pid=6763
题意:
给定一个无向图,每个点有一个点权
有这样的操作:选定一个连通块(每个点的权大于0),将所有节点的权值减一
求最少的操作次数,使得所有点的权为0
题解:并查集
依照题意,贪心地选最大的连通快,每当有点的权值减为0的时候可能会使得连通块断裂成多个,我们要维护这个过程
但是这样做会tle
那我们逆向进行这个过程:
1.权最大的点最后称为割点,所以我们按权值从大到小进行
2.当前的点为x,枚举x的所有邻接点y,如果y权比x大,就用并查集维护连通关系
具体上,我们并查集的顶点设为当前连通快中权值最小的点,每次连接fa[y]=x, ans+=val[y]-val[x];
3.最后判断有多少个并查集,答案加上并查集顶点的权值
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int cnt,head[maxn],to[maxn<<2],nxt[maxn<<2];
int a[maxn],f[maxn],v[maxn],p[maxn];
long long ans;
void add(int x,int y){
nxt[++cnt]=head[x]; head[x]=cnt; to[cnt]=y;
}
bool cmp(int x,int y){
return a[x]>a[y];
}
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
int main()
{
int t,x,y,n,m,yf;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m); cnt=0; ans=0;
for(int i=1;i<=n;i++)scanf("%d",&a[i]),p[i]=i,f[i]=i,v[i]=0,head[i]=0;
sort(p+1,p+1+n,cmp);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
for(int j=1;j<=n;j++){
x=p[j]; v[x]=1;
for(int i=head[x];i;i=nxt[i]){
y=to[i];
if(!v[y])continue;
yf=find(y);
if(yf==x)continue;
ans+=a[yf]-a[x];
f[yf]=x;
}
}
for(int i=1;i<=n;i++)v[i]=0;
for(int i=1;i<=n;i++){
x=find(i);
if(v[x])continue;
ans+=a[x]; v[x]=1;
}
printf("%lld\n",ans);
}
return 0;
}