POJ 1308 Is It A Tree?【判断树 并查集】

POJ 1308 Is It A Tree?

题目链接:vjudge传送门

题目大意:
给定若干条有向边,判断所给的所有边是否能组成一颗树(包含空树)

具体思路:
离散数学图论知识,只有一个结点入度为0,其它结点入度均为1的图是树,保存每个点的入度,最后判断即可

也可按树的定义来,用并查集来维护
新加入的边,被指向的结点必须是并查集中的祖先结点(排除入度大于1和环的情况),加入完后把两点合并
处理完后判断是否为空树或者森林即可。
下方分别贴出两种方式的代码

具体代码:

//保存入度
#include
#include
#include
#include
using namespace std;
const int N = 1e4;
int fa[N], visit[N],degree[N];

void init()
{
     
	memset(visit, 0, sizeof(visit));
	memset(degree, 0, sizeof(degree));
	for (int i = 1; i <= N; i++)
		fa[i] = i;
}
int main()
{
     
	int cnt = 1, flag = 1;
	int n, m;
	init();
	while (~scanf("%d%d", &n, &m))
	{
     
		if (m == -1 && n == -1)break;
		if (!(m == 0 && n == 0)) 
		{
     
			visit[m] = 1, visit[n] = 1;
			degree[m]++;
		}
		else	//判断空树或森林的情况
		{
     
			int first = 1, f;
			int isNull = 1, isForest = 0;
			for (int i = 1; i <= N; i++)if (visit[i])isNull = 0;	//考虑空树的情况
			if (!isNull) {
     	//不是空树则判断是否为森林
				int zeroDeg = 0;	//入度为0的点
				for (int i = 1; i <= N; i++)
				{
     
					if (visit[i]) {
     
						if (degree[i] == 0)zeroDeg++;	//记录入度为0的点的个数
						if (degree[i] > 1) {
     	//每个点的入度都小于2
							isForest = 1;
							break;
						}
					}
				}
				if (zeroDeg != 1)isForest = 1;	//树只有一个入度为0的点
			}
			if (isNull)flag = 1;	//空树
			if (isForest)flag = 0;	//森林
			if (flag) printf("Case %d is a tree.\n", cnt++);
			else printf("Case %d is not a tree.\n", cnt++);
			init();
			flag = 1;
		}
	}
	return 0;
}
//并查集
#include
#include
#include
#include
using namespace std;
const int N = 1e6;
int fa[N], visit[N];
void init()
{
     
	memset(visit, 0, sizeof(visit));
	for (int i = 1; i <= N; i++)
		fa[i] = i;
}
int find(int son)
{
     
	if (son == fa[son])
		return son;
	return fa[son] = find(fa[son]);
}
void unite(int u, int v)
{
     
	int r1 = find(u);
	int r2 = find(v);
	if (r1 == r2) return;
	fa[r2] = r1;
}
int main()
{
     
	int cnt = 1, flag = 1;
	int n, m;
	init();
	while (~scanf("%d%d", &n, &m))
	{
     
		if (m == -1 && n == -1)break;
		if (!(m == 0 && n == 0))
		{
     
			visit[m] = 1, visit[n] = 1;
			if (fa[m] == fa[n])flag = 0;	//新加入的边,from->to,to所属集合不能和from相同
			else if (fa[m] != m)flag = 0;	//新加入的边,被指向的必须是祖先结点
			else unite(n, m);
		}
		else
		{
     
			//判断空树或森林的情况
			int root = 0;
			int isNull = 1, isForest = 0;;
			for (int i = 1; i <= N; i++) {
     
				if (visit[i] && fa[i]==i) {
     
					root++;
					isNull = 0;
					if (root > 1) {
     
						isForest = 1;
						break;
					}
				}
			}
			if (isNull)flag = 1;	//空树
			if (isForest)flag = 0;	//森林
			if (flag) printf("Case %d is a tree.\n", cnt++);
			else printf("Case %d is not a tree.\n", cnt++);
			init();
			flag = 1;
			/*
			之前一直WA的代码,原来是fa[i]并不是总等于find(i),
			搞不懂,要是路径压缩算法正确,同一个集合的点不都是指向自己的祖先结点吗
			//判断空树或森林的情况
			int first = 1, f;
			int isNull = 1;
			for (int i = 1; i <= N; i++) {
				if (visit[i]) {
					isNull = 0;
					if (first) {
						f = find(i);	//一开始这里是写fa[i],WA了无数次
						first = 0;
					}
					else if (f != find(i)) {	//一开始这里是写fa[i]
						flag = 0;	//森林
						break;
					}
				}
			}
			if (isNull)flag = 1;	//空树
			if (flag) printf("Case %d is a tree.\n", cnt++);
			else printf("Case %d is not a tree.\n", cnt++);
			*/
		}
	}
	return 0;
}

你可能感兴趣的:(并查集,OJ题解)