http://acm.hdu.edu.cn/showproblem.php?pid=1856
More is better
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 327680/102400 K (Java/Others)Total Submission(s): 11695 Accepted Submission(s): 4332
Mr Wang selected a room big enough to hold the boys. The boy who are not been chosen has to leave the room immediately. There are 10000000 boys in the room numbered from 1 to 10000000 at the very beginning. After Mr Wang's selection any two of them who are still in this room should be friends (direct or indirect), or there is only one boy left. Given all the direct friend-pairs, you should decide the best way.
4 1 2 3 4 5 6 1 6 4 1 2 3 4 5 6 7 8
4 2HintA and B are friends(direct or indirect), B and C are friends(direct or indirect), then A and C are also friends(indirect). In the first sample {1,2,5,6} is the result. In the second sample {1,2},{3,4},{5,6},{7,8} are four kinds of answers.
有10000000壮丁(下标1 to 10000000),要抓一些壮丁出来,抓出来的壮丁必须直接或间接有联系,求最多能抓多少壮丁。
第二次做这题,发现这作为学习并查集的例题实在是太合适了
Q:什么是并查集?
A:并查集是一种用来管理元素分组情况的数据结构。并查集可以高效地进行查询和合并。
Q:并查集是用什么结构的呀?
A:树形结构,不过不是二叉树。
对于本题,我们将有直接或间接联系的放入同一棵树(使他们具有相同的根)并更新树的大小,同时记录最大树的规模。
如样例1:
4
1 2
3 4
5 6
1 6
首先初始化,每个结点都是根结点(a[i]=i,a[i]记录结点i的父结点)
读入1 2时,我们先查询1 2是否位于同一颗树内(根结点是否相同),发现根结点不同,建立树1-2(前者为根结点);
读入3 4时,同理,建立树3-4;
读入5 6时,同理,建立树5-6;
读入1 6时,我们发现1的根结点为1,6的根节点为5,建立树1-5,
则这颗树变为1-(2,5-6),规模为4。
Q:如何查询根结点?
A:用递归,直到a[i]=i时。
Q:如何合并两个结点?
A:将一个结点的父结点连到另一个结点的父结点。
关于优化:
如果将这棵树的高度减小,就能减少递归次数,从而缩短查根时间。
方法:
1.合并时,高度较小的树并入高度较大的树(需要开一个数组记录高度);
2.通过路径压缩——对于每个结点,一旦走到一次根结点,将其父结点直接改为根结点。
如果加入这两个优化,并查集的效率非常高,复杂度为阿克曼函数的反函数比O(log(n))还要快。
对于本题,由于内存限制,我们无法开一个数组记录高度,所以这优化就偷懒不做了。
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#include<cctype>
#include<cmath>
#include<string>
#include<cstring>
#include<queue>
#include<vector>
#define sqr(x) (x)*(x)
#define INF 0x1f1f1f1f
#define PI 3.1415926535
#define LL long long
#define mm 10000001
using namespace std;
int a[mm],sizetree[mm],ans; //a记录父结点,sizetree记录数的规模
int n;
void init()
{
for (int i=0;i<mm;i++)
{
a[i]=i;
sizetree[i]=1;
}
}
int root(int child) //递归查询根结点
{
if (a[child]==child)
return child;
else
return a[child]=root(a[child]); //优化,直接连根结点
}
void intree(int x,int y)
{
int rx,ry;
rx=root(x);
ry=root(y);
if (rx!=a[x])
a[x]=rx;
a[ry]=rx;
if (ry!=rx)
sizetree[rx]+=sizetree[ry];
ans=max(ans,sizetree[rx]);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
init();
ans=1; //注意当n=0时输出1
for (int i=0;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
intree(x,y);
}
printf("%d\n",ans);
}
return 0;
}