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] );上述第一个等式的意义是继续计算新连通分量的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; }