杭电多校2020第二场1001

杭电多校2020第二场1001

建议一边看代码一边理解会快一点哦
这个题目开始的时候,一直想用dfs做,做了五个小时还是错了。没想到用的是并查集!!!对于一个连通块来说,可以一直操作 min(a[i],是连通块内权值最小的那个点),然后最小的那些点就会变成0,此时可能会让连通块分裂成多个连通块。这种删除这个点得到多个连通块的情况不好处理。
可以反过来想:处理添加边把连通块融合的情况。那就从最大的点开始倒着添加点。首先每步刚开始的时候就加上这个点的亮度,再遍历这个点的每条边,如果加了这个边使得两个连通块融合,两个连通可以通过这个桥梁使得两边的连通分量可以共享a[i]次减少的,所以在后面的时候就是可以把这个这个重复的剪掉。(这个点的值会被在不同的分支里减掉多次,如果本来这两个连通分支通过另外一条边相连了,因为他加入点从大的开始,所以就无需考虑这个小点的贡献了)

#include
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
#define P pair
using namespace std;
const int maxn = 1e5 + 5;
vector<int> g[maxn];
int fa[maxn];
P a[maxn];
int n, m;
int vis[maxn];
int fnd(int x){
    if(fa[x] == x) return x;
    return fa[x] = fnd(fa[x]);
}
void link(int x, int y){
    int rx = fnd(x), ry = fnd(y);
    if(rx != ry) fa[rx] = ry;
    return;
}
void init(){
    scanf("%d%d", &n, &m);
    fors(i, 1, n+1) scanf("%d", &a[i].first),a[i].second = i, g[i].clear(), fa[i] = i, vis[i] = 0;
    while(m--){
        int u, v; scanf("%d%d", &u, &v);
        g[u].pb(v); g[v].pb(u);
    }
}
void sol(){
    sort(a+1,a+1+n);
    ll ans = 0;
    for(int i = n; i > 0; --i){
        ans += a[i].first;  ///首先全部加上这个数值
        int u = a[i].second;
        for(int v: g[u]){
            if(!vis[v]) continue;  ///没有被问过的时候相当于就是这个亮度一定比现在在访问的这个点亮
            int rt = fnd(v);
///看看他们是否在同一个连通分量里,如果不是的话,相当于两个连通分量通过这个桥梁共享a[i]次
            if(fnd(u) != rt) {
                link(u, v); ans -= a[i].first;///所以将这多余减掉,因为后面加入的一定比前面加入的要大,所以在这个连通分量里肯定会消耗a[i]次
            }
        }
        vis[u] = 1;
    }
    printf("%lld\n", ans);
}
int main()
{
    int T; cin>>T;
    while(T--){
        init();
        sol();
    }
}


你可能感兴趣的:(并查集)