并查集 Total Eclipse
题解:
官方题解。
自己看完题解的整理:
这个题目明显就是选一个连通块,然后减去这个连通块的最小值,所以变成0的节点要删除,就会分裂成多个连通块。
实现方法就是对b进行排序,从大到小放,每次排序遍历一次这个点 x 的所有的边,对于(x,y) ,如果y在x之前放进去并且y和x没有联通,那么就把x放到y这棵树的根节点的父亲节点,最后答案就是这棵树的儿子节点的b和父亲节点的b的差值,接下来说一下为什么这样是对的。
因为我们首先放进去的是b大的值,这个是最后删去的,按照b从大到小放进去,每次放入一个x,增加一条边(x,y)
-
那么如果y之前放进去过,并且他的根节点就是本身,是不是说明这个x点是y所在连通块中y连到的数值最大的一个点,如果删去了这个x点,那么y就变成了一个孤立的点(因为这个x点是数值最大的点,即使y连了其他的点,那么数值也没有大于x这个点,所以当x点删去,其他点肯定已经删去了),那么此时删去y这个点的代价就是 y-x。
-
如果y之前放进去过,并且它的根节点不是他本身,那么就说明这个x点是y所在联通块中一个普通的点,但是他肯定对于另一个点v来说是一个最大的点,所以x对y的影响不大,但是对v来说影响很大,这个v就是当前这个y所在节点的根节点。举个例子:假设v和y已经连号了,v是y的根节点,说明b[v]<b[y] ,那么加入这个x,b[x]<b[v] ,假设x被删除,那么再删去v这个节点的代价就是 b[v]-b[x] 。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
int head[maxn],to[maxn<<1],nxt[maxn<<1],cnt;
void add(int u,int v){
++cnt,to[cnt]=v,nxt[cnt]=head[u],head[u]=cnt;
++cnt,to[cnt]=u,nxt[cnt]=head[v],head[v]=cnt;
}
int f[maxn];
int findx(int x){
return x == f[x]?x:f[x]=findx(f[x]);
}
void unite(int x,int y){
x = findx(x);
y = findx(y);
if(x==y) return ;
f[y]=x;
}
bool same(int x,int y){
return findx(x)==findx(y);
}
int b[maxn],p[maxn];
void init(int n) {
cnt = 0;
for (int i = 0; i <= n; i++) head[i] = 0, f[i] = i, p[i] = i;
}
bool cmp(int i,int j){
if(b[i]==b[j]) return i<j;
return b[i]>b[j];
}
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
init(n);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
}
sort(p+1,p+1+n,cmp);
ll ans=0;
for(int i=1;i<=n;i++){
int u = p[i];
ans+=b[u];
for(int j=head[u];j;j=nxt[j]){
int v = to[j];
//如果没有放进去就continue
if(b[v]<b[u]) continue;
if(b[u]==b[v]&&v>u) continue;
if(!same(u,v)){
unite(u,v);
ans-=b[u];
}
}
}
printf("%lld\n",ans);
}
return 0;
}