ZOJ 3659 Conquera New Region(并查集:维护根节点信息)

ZOJ 3659 Conquera New Region(并查集:维护根节点信息)

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3659

题意:

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

        输入:多组实例。每个实例第一个为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节点为根的连通分量所具有的节点数目。

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

        首先将所有的边按边长从大到小排序,然后每次优先取边长大的边。用该边X来合并连通分量时,被合并的两个分量A和B之间的任意两个点u(属于A)和v(属于B)的S[u][v]一定等于边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值即可。

AC代码(新):

#include<cstdio>
#include<cstring>
#include<algorithm>
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=1;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;
}

AC代码:440ms

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=200000+1000;
int pa[MAXN];//所属连通分量
long long sum[MAXN];//sum[i]=x表第i个连通分量中的最优点到该分量其他所有点的容量之和为x
int num[MAXN];//连通分量i中的节点个数
int n;
struct edge
{
    int a,b,len;
    bool operator <(const edge & B)const
    {
        return len>B.len;
    }
}edges[MAXN];
int findset(int x)
{
    if(pa[x]==-1)return x;
    return pa[x]=findset(pa[x]);
}
int main()
{
    while(scanf("%d",&n)==1&&n)
    {
        for(int i=1;i<=n;i++)//初始化所有点
        {
            pa[i]=-1;
            num[i]=1;
            sum[i]=0;
        }
        for(int i=0;i<n-1;i++)
            scanf("%d%d%d",&edges[i].a,&edges[i].b,&edges[i].len);
        sort(edges,edges+n-1);//边长从大到小排序
        long long ans=0,atob,btoa;
        for(int i=0;i<n-1;i++)
        {
            int fa = findset( edges[i].a );
            int fb = findset( edges[i].b );
            int len = edges[i].len;
            atob = (long long)len*num[fa]+sum[fb];//a的分量合并入b中
            btoa = (long long)len*num[fb]+sum[fa];//b的分量合并入a中
            if(atob > btoa)
            {
                pa[fa]=fb;
                num[fb]+=num[fa];
                sum[fb]=atob;
            }
            else
            {
                pa[fb]=fa;
                num[fa]+=num[fb];
                sum[fa]=btoa;
            }
        }
        ans = max(ans, max(atob,btoa) );
        printf("%lld\n",ans);
    }
    return 0;
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值