Total Eclipse(并查集)

Total Eclipse(并查集)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6763

题目大意:

有一个n个点m条边的无向图,结点从1开始编号到n,每个结点都有一个权值bi,现在希望可以将所有结点的权值改为0。可以进行下面的操作:选择一个正整数k(1<=k<=n),选择k个不同的结点c1, c2, ..., ck(1<=ci<=n),并且这k个结点可以相互连通,也就是这k个点是连通的,这k个城市每个城市的权值都减1。点权不能为负,且每次找的必须是极大连通分量。问最小需要多少次这样的操作可以将所有结点的权值都变为0。

官方题解:

每次一定是选择一个极大连通块,将里面所有数同时减小,直到最小值变成 0,然后将变 成 0 的点删除,分裂成多个连通块再接着做。

为了实现这个策略,可以将整个过程倒过来看,变成按照 b 的值从大到小依次加入每个点。 加入每个点 x 时遍历与 x 相连的所有边 (x,y),如果 y 在 x 之前加入且 x 和 y 不连通则将 x 和 y 合并,并将 y 所在连通块的树根的父亲设为 x,得到一棵有根树。那么每个点 x 在成为最小值之前已经被做了 bfatherx 次操作,所以每个点 x 对答案的贡献为 bx − bfatherx。 使用并查集支持路径压缩,时间复杂度 O((n + m)logn)。

理解:

先是一起对整个极大连通分量中的所有点的权值进行减少,然后有的点权变为0,这个点可看成被删除了,然后这个大的连通分量就变成了许多小的连通分量,然后继续删。

对于一个连通块来说,一定是点按照权值从小到大被删。把操作顺序倒过来,就是把大的结点减小成和小的结点相同,然后一起删掉。

 

代码如下:

#include <bits/stdc++.h>
typedef long long LL;
#define pb push_back
#define mst(a) memset(a,0,sizeof(a))
const int INF = 0x3f3f3f3f;//0x7fffffff;
const double eps = 1e-8;
const int mod = 1e9+7;
const int maxn = 1e5+10;
using namespace std;

struct edge
{
    int to;
    int next;
}E[10*maxn];//注意边的条数,无向图至少要开两倍
int head[maxn], tot;
void add(int u,int v)
{
    E[tot].to = v;
    E[tot].next = head[u];
    head[u] = tot++;
}

int n,m;
int fa[maxn];
int Find(int x)
{
    return x==fa[x]? x : fa[x]=Find(fa[x]);
}
void Union(int a,int b)
{
    int aa = Find(a);
    int bb = Find(b);
    if(aa!=bb) fa[aa] = bb;
}

int pre[maxn];//每个点的父亲节点,即之前的连通分量减到了这个点的权值
int rk[maxn];//权值第i大的点的编号
int wake[maxn];//标记每个点是否在连通分量中
int a[maxn];//点的权值

init()
{
    tot = 0;
    for(int i=0;i<=n;i++)
    {
        head[i] = -1;
        pre[i] = wake[i] = 0;
        rk[i] = fa[i] = i;
    }
}

bool cmp(int x, int y)
{
    return a[x]>a[y];
}

int main()
{
    #ifdef DEBUG
    freopen("sample.txt","r",stdin); //freopen("data.out", "w", stdout);
    #endif
    
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d %d",&n,&m);
        init();
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d %d",&u,&v);
            add(u,v); add(v,u);
        }
        sort(rk+1,rk+1+n,cmp);
        for(int i=1;i<=n;i++)
        {
            int t = rk[i]; //选择当前权值最大的一个点
            wake[t] = 1;  //将其唤醒
            for(int j=head[t];j!=-1;j=E[j].next)
            {
                int v = E[j].to;
                if(!wake[v]) continue;
                int vv = Find(v);
                if(vv == t) continue;
                pre[vv] = fa[vv] = t;
            }
        }
        LL ans = 0;
        for(int i=1;i<=n;i++)
            ans += a[i]-a[pre[i]];
        //因为之前的连通分量所有点的权值全为a[i],他们加上了pre[i]这个点,权值就全变成a[pre[i]]了,故对ans贡献值为a[i]-a[pre[i]]
        printf("%lld\n",ans);
    }
    
    return 0;
}

 

 

-

posted @ 2020-07-24 01:07  jiamian22  阅读(360)  评论(0)    收藏  举报