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;
}