题目
题目大意就是在一张图上,有n个节点,m条无向边。每个节点有一个亮度值,每次可以选择一个连通块,块中所有节点值-1.问多少次操作可以使得图上所有节点的点权为0.
排序+并查集
需要注意的是,已经成为0的点不能再被选入连通块中,否则就成为负数了。因此当一个点其点权成为0的时候就相当于点本身及以其为顶点的边被删除了,不能对后面的过程产生影响。
仔细分析一下,整个过程就是不断的挑选连通块,操作其中点权最小值次,过后把点权为0的点删除,形成更多的连通块,往复这个过程,知道所有的点点权都为0.
一种可行的思路是这样:反向执行上述过程,将所有点排序,从点权大的点开始遍历起,使其不断连接不同的连通块(仅考虑边终点权值比当前点小,即仅考虑从小点向大点连接)。
当其连接上一个连通块时,对答案的贡献就是该连通块中点权的最小值与当前点点权的差。维护连通块可使用并查集来解决
答案统计除去在合并时产生的部分,还需要在最后加上所有连通块中最小点权的值。
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#define N 100050
#define M 200050
using namespace std;
struct V{
int x;
int next;
}nxt[M];
struct P{
int w;
int code;
int head;
bool operator < (const P& a) const{
return w < a.w;
}
bool operator <= (const P& a) const{
return w <= a.w;
}
}point[N];
int fa[N];
int cToP[N];
int n,T,m;
int tot;
long long ans;
/*连接两个点,x->y*/
void link(int x,int y){
nxt[++tot].x = y;
nxt[tot].next = point[x].head;
point[x].head = tot;
}
bool cmp(const P&a,const P&b){
return b < a;
}
int find(int k){
if(fa[k] == k)
return k;
fa[k] = find(fa[k]);
return fa[k];
}
int main(){
for(cin >> T;T != 0;T--){
//读入nm
// cin >> n >> m;
scanf("%d%d",&n,&m);
//初始化边总数和答案
tot = 0;
ans = 0;
//读入点数据
for(int i = 1;i <= n;i++){
// cin >> point[i].w;
scanf("%d",&point[i].w);
point[i].code = i;
point[i].head = 0;
fa[i] = i;
}
//读入边数据
//小的指向大的,相等互相指
for(int i = 1,x,y;i <= m;i++){
// cin >> x >> y;
scanf("%d%d",&x,&y);
if(point[x] <= point[y]){
link(x,y);
}
if(point[y] <= point[x]){
link(y,x);
}
}
/*给点按照权重排序,降序*/
sort(point + 1,point + 1 + n,cmp);
/*code序号对应排序后的位置*/
for(int i = 1;i <= n;i++){
cToP[point[i].code] = i;
}
/*for(int i = 1;i <= n;i++){
cout << point[i].code << " " << point[i].w << endl;
}*/
P p,q;
for(int i = 1;i <= n;i++){
p = point[i];
for(int j = p.head;j;j = nxt[j].next){
q = point[cToP[nxt[j].x]];
/*已经在并查集中*/
if(find(p.code) == find(q.code))
continue;
ans += point[cToP[find(q.code)]].w - p.w;
fa[find(q.code)] = p.code;
}
}
for(int i = 1;i <= n;i++){
ans += fa[i] == i ? point[cToP[i]].w : 0;
}
cout << ans << endl;
// printf("%d\n",ans);
}
}