2020杭电多校第二场A题
题意是有n个城市,之间有m条边,每个城市有一个bi属性,每选择一个点,与之联通的点的bi都会减一,问最少操作几次可以使全部bi为0。
思路是每次都选择最大的连通块,当出现一个bi=0后分为很多个更小的连通块,按照同样的方法操作。正着实现很麻烦,所以考虑倒过来的方法。
那么就可以将每次都把bi删为0看成一个个属性为bi的点插入进去。所以点插入的顺序应当是bi从大到小,因为正着来的话就是bi越小的点越早退出。
加入每个点 x 时遍历与 x 相连的所有边 (x,y),如果 y 在 x 之前加入且 x 和 y 不连通则将 x 和 y 合并,并将 y 所在连通块的树根的父亲设为 x,得到一棵有根树。那么每个点 x 在成为最小值之前已经被做了 b[father[x]] 次操作,所以每个点 x 对答案的贡献为 b[x] −b[father[x]]。 使用并查集支持路径压缩,时间复杂度 O((n + m)logn)。
具体实现如下:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string>
#include<stack>
#include<queue>
#include<vector>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e5+10;
const int M = 2e5+10;
const int INF = 0x3f3f3f3f;
const int inf = - INF;
const int mod = 1e9+7;
const double pi = acos(-1.0);
int n,m,cnt;
ll ans;
int edge[M<<1],nxt[M<<1],head[N];
int w[N],vis[N],f[N],fa[N],node[N];
void add(int x,int y){
edge[++cnt]=y;nxt[cnt]=head[x];head[x]=cnt;
edge[++cnt]=x;nxt[cnt]=head[y];head[y]=cnt;
}
bool cmp(int x,int y){
return w[x]>w[y];
}
int find(int x){
//printf("%d\n",fa[x]);
return f[x]==x?x:f[x]=find(f[x]);//并查集
}
void init(){
cnt=0;
ans=0;
for(int i=1;i<=n;i++){
f[i]=i;
fa[i]=0;
vis[i]=0;
head[i]=0;
}
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
node[i]=i;
}
int x,y;
while(m--){
scanf("%d %d",&x,&y);
add(x,y);
}
sort(node+1,node+1+n,cmp);
for(int i=1;i<=n;i++){
x=node[i];
vis[x]=1;
for(int j=head[x];j;j=nxt[j]){
y=edge[j];
//printf("%d %d\n",x,y);
if(!vis[y]) continue;//还未加入的点
y=find(y);
if(y==x) continue;//已经是x的子节点
fa[y]=f[y]=x;//设x为y的父节点
}
}
for(int i=1;i<=n;i++)
ans+=w[i]-w[fa[i]];
printf("%lld\n",ans);
}
//system("pause");
return 0;
}
/*
1
3 2
3 2 3
1 2
2 3
*/