并查集--根节点维护

看到重影。本来打算跳过了,同学点提了两句,看下去,果然看明白了。

参看资料:https://blog.csdn.net/u013480600/article/details/44131453


题目1》POJ 1611 The Suspects【并查集+连通分量的根节点维护】

https://blog.csdn.net/u013480600/article/details/21001447

        【一道简单的根节点维护,维护的数量,依靠两个数组打配合,最后找到答案,看了题目就明白了。】

题意:

        现在有n个学生(从0号到n-1号),其中0号学生是有可能非典的,只要和被怀疑有非典的学生在一个社团的学生都是有可能有非典的且需要被隔离,但是学校有很多社团,所以现在要你求一共有多少学生需要被隔离?

        输入:多组实例.每个实例第一行n和m (0 <n <= 30000 and 0 <= m <= 500),表示学生从0到n-1号,接下来有m行,每行描述一个社团的所有成员.每行首先是一个k,表示该社团有k个成员,接下来是这k个成员的编号.当一组实例n=0且m=0时,表示输入结束.

        输出:需要被隔离的学生数.

分析:

        所有学生被处理前都是独立的连通分量,以自己作为根。只要读入一个社团,那么社团中所有的学生所属的连通分量都要合并成1个。不断这么处理,最终和0号学生在同一个连通分量的所有学生都是需要被隔离的。

        AC代码(新)中用 根节点维护num[i]信息表示当前连通分量的节点数目,当所有节点合并完之后,只需要计算0号节点所在的连通分量的点数 输出即可。
 


题目二》ZOJ 3659 Conquera New Region【并查集+维护根节点信息】

https://blog.csdn.net/u013480600/article/details/20856189

【被讲解的阵仗吓到之后,硬着头皮看下去,才看明白了,这算一道真正的根节点维护吧】

题意:

        现在有N个城市,N-1条线段构成的树。C(i,j)表示如果城市i和城市j之间有一条线段,那么他们之间路的容量。S(i,j)表示从i到j的路径(一条路径 由 首尾相连的多条线段构成)的容量,其等于从i到j的路径经过的连续的所有线段中的最小线段容量。现在要找一个城市,使得它到其他N-1个点的S值之和最大。

【说的一点也不清楚,一开始就没让我看懂题,,

题目大意:

给定一个N个点的树,包括n-1条边,表述形式:i,j,s,表示 i 点到 j 点路径的容量为s ;该树任意两点之间的通路可能经过多条路径,则这条通路的最大容量受其包含路径中容量最小的那条路径限制,类似于短板效应,一条通路的最大容量即为其容量最小的那条路径的容量。

例:

2~4:最大容量为9;3~4:最大容量为6;1~4:最大容量为2;0~4:最大容量为2;

求从该图的某一点出发,到达其余n-1个点的最大容量和。

从不同的点出发,其容量和不同,从0出发,为10+2+2+2+2;从1出发,为10+2+2+2;从3出发:为2+2+6+6;从2出发:为2+2+6+9;从4出发,为2+2+2+6+9)

输入:多组实例。每个实例第一个为N. (1 ≤ N ≤ 200,000),接下来有N-1行,为a b c 描述从a城市到b城市的容量为c。

输出:那个最大S值之和。

     

分析:

        当然你可以用暴力方法计算出所有可能(i,j)的S[i][j]值,然后用O(n^2)的循环来找出所求值(当然肯定超时)。

        现在用另外一种方法做:

        我们用并查集来做,每个城市看成一个并查集的节点。且每个节点维护3种信息(下面3种信息只对当前根节点来说有效,不是根节点的3种信息是没用的且很可能是错误的):

       fa[i]:i节点的父节点编号(不一定是根节点)。

       num[i]:以 i 节点为根的连通分量所具有的节点数目【有效前提:i 是当前根节点】

       s[i]: 以 i 节点为根的连通分量中的一个最优点到该分量中其他所有点的容量值之和的最大值(如果该分量包含题目输入的所有点,那么该s值就是最终我们所求)【s[ i ]中存储的是该连通分量的“最大值”】

        首先将所有的边按边长从大到小排序,然后每次优先取边长大的边。用该边X来合并连通分量时,被合并的两个分量A和B之间的任意两个点 u(属于A) 和 v(属于B) 之间的最大容量一定等于边X的长【想想为什么,一会样例中会有解释】。

        合并A和B分量时,我们依然需要维护根节点的fa,num,以及s信息。可以令:

        s[A] = max( num[A]*X边长+s[B] , num[B]*X边长+s[A] );
        num[A] += num[B];
        fa[A] = B;
        上述第一个等式的意义是 继续计算新连通分量最大容量和S的值。假设该分量的最优点 i 取在A分量中,那么最大容量和S的值明显应该是S[A] + i点B分量上所有点的 s[i][v]值 (由上述分析可知此时s[i][v]肯定等于X的边长)。所以如果最优点i取在A分量中,新的S和值应该为num[A]*X边长+s[B]。如果最优点i取在B分量中,新的S和值应该为num[B]*X边长+s[A]。

        最终当全图只有一个连通分量时,我们直接输出当前分量根节点的s值即可。

样例:

已经有一个11个点10条边的图

1》按照处理思想,将每个节点视为孤立节点,将10条边按边长排序并取用:

       且三个数组初始化完毕:

2》将取用现在边长最大的边(1,0,8)加入图中, 


    注意到,父节点数组fa中父节点变化,0号节点和1号节点成为一个连通分量,根节点为0;如果这个不明白还是从最基础的应用看明白并查集的实现,这儿也就明白了,奶一口并查集基础思想:https://blog.csdn.net/sodacoco/article/details/84985321,

并且,表示连通分量元素个数的数组num,现在根节点正好0,则在num中可得到,以0为根节点的连通分量元素个数为2,并且s中,以当前根节点为连通分量的最大容量和为8【从1出发,到其余1个点,最大容量为8;从0出发同理,s只管最大值是多少,从谁那里得来的最大值它不管】。

3》再拿出第二大的边来(4,5,6):

此步分析同上。

4》第三大的边(1,4,4)【注意看这一步,代表了之后的每一步】;

       首先是两个独立的连通分量{0,1}、{4,5} 合并为一个连通分量,此时根节点变为5,则在num数组中,以5为根节点的连通分量的元素数目为4,但现在s是怎么算出来的呢?这就是重头戏了:

       公式为s=max( num[A]*X边长+s[B] , num[B]*X边长+s[A] );

       此时边的长度为4,num[A]=2 表示{0,1}中的元素个数,s[B]表示B中最大的容量,为;num[B],s[A]同理;

       这样看,为什么从一个集合到另一个集合中的任意一对元素的路径的最大容量都为X?因为X一定小于已经存在的“最大容量”,这也就是为什么我们从大到小取出元素,确保当前路径在图中最小,则两连通分量之间任意路径最大容量受其制约为X。用当前连通分量【A/B】的最大容量和【即为S[本连通分量] 】,加上另一连通分量所有元素路径的最大容量【等于X】的和【即为X*n】。共有两个连通分量,则从两种可能取值较大的和作为当前根节点连通分量的最大容量和

5》其余各边连接过程同上。

测试代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=200000+5;

//并查集
int fa[maxn];      //father
int num[maxn];     //本连通分量节点数
long long s[maxn]; //本连通分量的S值
int findset(int x)
{
cout<<"      findset("<<x<<")"<<endl;
    return fa[x]==-1 ? x: fa[x]=findset(fa[x]);
}
void bind(int u ,int v,int cost)
{
cout<<"   bind("<<u<<","<<v<<","<<cost<<")"<<endl;
cout<<"   fa[]=";
for(int i=0;i<=10;i++){
    cout<<fa[i]<<"  ";
}cout<<endl;
    int fu=findset(u);
    int fv=findset(v);
cout<<"   fu="<<fu<<"  fv="<<fv<<endl;
    if(fu!=fv)
    {
cout<<"   s["<<fv<<"]=max(num["<<fv<<"]*"<<cost<<"+s["<<fu<<"],"
    <<"num["<<fu<<"]*"<<cost<<"+s["<<fv<<"])"<<endl;
cout<<"       =max("<<num[fv]<<"*"<<cost<<"+"<<s[fu]<<","
    <<num[fu]<<"*"<<cost<<"+"<<s[fv]<<")"<<endl;
cout<<"       =max("<<num[fv]*cost+s[fu]<<","
    <<num[fu]*cost+s[fv]<<")"<<endl;
        s[fv] = max( (long long)num[fv]*cost+s[fu] , (long long)num[fu]*cost+s[fv] );

cout<<"       ="<<s[fv]<<endl;

        num[fv] += num[fu];
        fa[fu] = fv;

cout<<"   fa[]=";
for(int i=0;i<=10;i++){
    cout<<fa[i]<<"  ";
}cout<<endl;

cout<<"   num[]=";
for(int i=0;i<=10;i++){
    cout<<num[i]<<"  ";
}cout<<endl;

cout<<"   s[]=";
for(int i=0;i<=10;i++){
    cout<<s[i]<<"  ";
}cout<<endl<<endl;

    }

}

struct Edge
{
    int u;
    int v;
    int cost;

    bool operator<(const Edge& rhs)const
    {
        return cost>rhs.cost;
    }
}edges[maxn];

int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        for(int i=0;i<=n;i++)
        {
            fa[i]=-1;
            num[i]=1;
            s[i]=0;
        }

        for(int i=0;i<n-1;i++)
            scanf("%d%d%d",&edges[i].u,&edges[i].v,&edges[i].cost);

        sort(edges,edges+n-1);
cout<<"edges[]= "<<endl;
for(int i=0;i<n-1;i++){
cout<<edges[i].u<<"  "<<edges[i].v<<"  "<<edges[i].cost<<endl;
}
cout<<"fa[]=";
for(int i=0;i<=10;i++){
    cout<<fa[i]<<"  ";
}cout<<endl;

cout<<"num[]=";
for(int i=0;i<=10;i++){
    cout<<num[i]<<"  ";
}cout<<endl;

cout<<"s[]=";
for(int i=0;i<=10;i++){
    cout<<s[i]<<"  ";
}cout<<endl<<endl;

        for(int i=0;i<n-1;i++){
cout<<"edges["<<i<<"].u="<<edges[i].u<<"  edges["<<i<<"].v="<<edges[i].v
    <<"  edges["<<i<<"].cost="<<edges[i].cost<<endl;
            bind(edges[i].u, edges[i].v, edges[i].cost);
        }

        printf("%lld\n", s[findset(1)]);

    }
    return 0;
}


/*
11
1 2 2
3 4 2
5 6 2
7 8 2
9 10 2
1 4 4
5 8 4
0 9 4
4 5 6
1 0 8
*/

测试结果:

真正代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=200000+5;

//并查集
int fa[maxn];      //father
int num[maxn];     //本连通分量节点数
long long s[maxn]; //本连通分量的S值
int findset(int x)
{
    return fa[x]==-1 ? x: fa[x]=findset(fa[x]);
}
void bind(int u ,int v,int cost)
{
    int fu=findset(u);
    int fv=findset(v);
    if(fu!=fv)
    {
        s[fv] = max( (long long)num[fv]*cost+s[fu] , (long long)num[fu]*cost+s[fv] );
        num[fv] += num[fu];
        fa[fu] = fv;
    }

}

struct Edge
{
    int u;
    int v;
    int cost;

    bool operator<(const Edge& rhs)const
    {
        return cost>rhs.cost;
    }
}edges[maxn];

int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        for(int i=0;i<=n;i++)
        {
            fa[i]=-1;
            num[i]=1;
            s[i]=0;
        }

        for(int i=0;i<n-1;i++)
            scanf("%d%d%d",&edges[i].u,&edges[i].v,&edges[i].cost);

        sort(edges,edges+n-1);

        for(int i=0;i<n-1;i++){
            bind(edges[i].u, edges[i].v, edges[i].cost);
        }

        printf("%lld\n", s[findset(1)]);

    }
    return 0;
}

 

睡觉!

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值