题目1481:Is It A Tree?

本文详细阐述了如何通过并查集算法判断一组节点和边组成的集合是否满足树的定义,包括特殊情况的处理。

A tree is a well-known data structure that is either empty (null, void, nothing) or is a set of one or more nodes connected by directed edges between nodes satisfying the following properties.

There is exactly one node, called the root, to which no directed edges point.
Every node except the root has exactly one edge pointing to it.
There is a unique sequence of directed edges from the root to each node.
For example, consider the illustrations below, in which nodes are represented by circles and edges are represented by lines with arrowheads. The first two of these are trees, but the last is not.


In this problem you will be given several descriptions of collections of nodes connected by directed edges. For each of these you are to determine if the collection satisfies the definition of a tree or not.

输入:

The input will consist of a sequence of descriptions (test cases) followed by a pair of negative integers. Each test case will consist of a sequence of edge descriptions followed by a pair of zeroes Each edge description will consist of a pair of integers; the first integer identifies the node from which the edge begins, and the second integer identifies the node to which the edge is directed. Node numbers will always be greater than zero and less than 10000.

输出:

For each test case display the line "Case k is a tree." or the line "Case k is not a tree.", where k corresponds to the test case number (they are sequentially numbered starting with 1).

样例输入:
6 8  5 3  5 2  6 4
5 6  0 0

8 1  7 3  6 2  8 9  7 5
7 4  7 8  7 6  0 0

3 8  6 8  6 4
5 3  5 6  5 2  0 0
-1 -1
样例输出:
Case 1 is a tree.
Case 2 is a tree.
Case 3 is not a tree.

题意:判断所给的数据能否组成一棵树

思路:并查集,用vex记录顶点数,edge记录边数,root记录树根数,若vex>1且vex-edge=1和root=1,能成树。  

要注意几种特殊情况:

0 0 空树是一棵树

1 1 0 0不是树,不能自己指向自己

1 2 1 2 0 0 不是树,重复不行

1 2 2 3 3 4 4 5 5 1 0 0 不是树,一个节点指向自己父亲或祖先都是错误的,即5->1错

1 2 2 1 0 0也是错误的

P.s:控制数据结束的条件是x,y<0,不是x==-1&&y==-1,如果超时了,应该就是这个错误。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int a[10001];

int find(int x) //找祖先
{
	while(a[x] != x)
		x = a[x];
	return x;
}

void merge(int x,int y)//合并
{
	int t = find(x);
	int h = find(y);
	a[h]=t;
}

int  main()
{
	int n = 0,m = 0,s = 0,b,c,k = 0;
	memset(a,0,sizeof(a));
	while(cin >> b >> c)
	{
		if(b < 0) 
			break;
		else if(n == 0 && m == 0 && b == 0 && c == 0)
		{
			printf("Case %d is a tree.\n",++k);
			continue;
		}
		else  if(b == 0 && c == 0)
		{
			k++;
			for(int i = 1; i < 10001; i++)
			{
				if(a[i])
					m++;
				if(a[i]==i)
					s++;
			}
			if(s==1 && n==m-1)
				printf("Case %d is a tree.\n",k);
			else
				printf("Case %d is not a tree.\n",k);
			n=0,m=0,s=0;
			memset(a,0,sizeof(a));
		}
		else
		{
			if(!a[b])
				a[b]=b;
			if(!a[c])
				a[c]=c;
			merge(b,c);
			n++;
		}
	}
	return 0;
}


A-4 Is It A Treec++题目,代码不要有注释) 分数 30 作者 陈越 单位 浙江大学 By definition, a tree is a collection of nodes. The collection can be empty; otherwise, a tree consists of a distinguished node r, called the root; and zero or more nonempty (sub)trees, each of whose roots are connected by a directed edge from r. In other words, a tree is a spacial case of a directed graph. Given a directed graph, your job is to tell whether or not it is a tree. Input Specification: Each input file contains one test case, which starts from a line containing a positive integer n (≤10 4 ), the number of nodes. Hence we assume that all the nodes are numbered from 1 to n. Then several lines follow, each describes a directed edge in the format source destination where both are the nodes' indices. It is guaranteed that there are no more than 10 4 edges, and that source is never the same as destination. The input ends with source being zero, and that line must not be processed. Note: duplicated edges are counted as ONE edge (as shown by the first sample). Output Specification: If the given graph is a tree, out put in a line yes root, where root is the index of the root. Or if not, output no k where k is the number of nodes with 0 indegree. Sample Input 1: 7 2 1 2 1 4 3 4 2 6 5 6 4 6 7 0 Sample Output 1: yes 6 Sample Input 2: 7 2 1 4 3 4 2 6 4 6 7 5 3 0 Sample Output 2: no 2 鸣谢柳汀洲补充数据! 代码长度限制 16 KB Java (javac) 时间限制 800 ms 内存限制 256 MB 其他编译器 时间限制 400 ms 内存限制 64 MB 栈限制 8192 KB
最新发布
08-11
<think>题目要求:判断一个有向图是否是一棵的条件是:只有一个根节点(入度为0),其余节点入度为1,并且从根节点可以到达所有节点(即连通,注意是有向图,所以是根节点能够到达所有节点)。 注意:题目中说明,重复的边算一条边(所以要去重),输入以0结束(0作为源节点时停止输入)。 思路: 1. 的条件: a. 只有一个入度为0的节点(根节点)。 b. 除根节点外,其他节点入度均为1。 c. 从根节点出发,可以到达所有节点(即整个图是连通的,且没有环,因为是无环的)。 但是注意:题目中只要求我们判断是不是,而在定义中是一个有向图,且满足: - 只有一个根(即入度为0的节点)。 - 其他节点有且只有一个父节点(即入度为1)。 - 从根节点开始,可以到达每一个节点(即连通,且没有其他独立的连通分量)。 然而,我们还需要注意,中不能有环。但是,如果我们满足:只有一个入度为0的节点,其他节点入度为1,并且边的数量等于节点数减1(因为有n-1条边)?但是题目没有给出边的数量,而且输入中可能有重复边,所以边的数量可能不是n-1(但去重后应该是n-1?)但是题目说重复边算一条,所以去重后边数应该是n-1?但是的条件并不一定要求边数等于n-1,因为也可以有0个节点(空),但是题目中节点数n>=1?题目说“the number of nodes”是正整数n,所以n>=1。 但是,注意题目定义:是一个节点集合,可以是空;否则有一个根节点和若干子。而题目输入中节点数n>=1(因为n>=1),所以空的情况不会出现(因为n>=1)。 然而,我们再看题目输入:输入以0结束,且0作为源节点时不处理。所以实际输入的边数可能不足n-1。 所以,我们需要检查: 条件1:整个图中,入度为0的节点有且仅有1个(记为root)。 条件2:除root外,其他所有节点的入度均为1。 条件3:从root出发,进行BFS或DFS,可以访问到所有n个节点(即连通)。 但是,条件2和条件3其实已经隐含了没有环(因为如果有环,那么环上的节点入度至少为2?或者导致无法访问所有节点?)。实际上,在满足条件1和条件2的情况下,如果从根节点出发能够访问所有节点,那么图中一定没有环。因为如果有环,那么环上必然有一个节点有两个不同的路径到达(即存在两个不同的路径,导致该节点入度至少为2),这与条件2(入度为1)矛盾。所以条件2已经保证了无环。 但是,我们还要注意:条件2要求除根节点外每个节点入度都是1,那么整个图中边的数量必须是n-1(因为每个节点(除了根)都有一条入边)。所以我们可以通过去重后的边数是否等于n-1来辅助判断?但是题目没有要求边数,所以我们可以不检查边数,而是检查入度。 因此,步骤: 1. 读入节点数n。 2. 用邻接表建图(注意去重,因为重复边算一条)。 3. 记录每个节点的入度(注意重复边只算一条,所以如果同一条边出现多次,只增加一次入度)。 4. 统计入度为0的节点个数: - 如果个数不为1,则输出"no k",其中k是入度为0的节点个数(注意:题目要求输出的是入度为0的节点个数,即使个数为1也要输出根节点,但这里我们判断个数不为1就输出no)。 5. 如果入度为0的节点个数为1,那么找到这个根节点,然后从根节点开始BFS/DFS遍历整个图,看是否能够访问所有节点(同时,在遍历过程中,我们可以检查是否满足每个节点(除了根)入度都为1?实际上,我们在遍历前已经通过入度数组统计了每个节点的入度,所以遍历前已经知道条件2是否满足?注意:条件2要求除根节点外,其他节点入度必须为1。所以我们在遍历前,可以检查:根节点入度为0,其他节点入度必须为1。如果有一个节点入度不为1(且不是根节点),那么就不满足条件。 但是,我们需要注意:有可能存在多个入度为0的节点(比如2个),那么我们就直接输出"no 2",不需要再检查连通性。所以步骤: 步骤1:统计每个节点的入度(建图时,对于每条边(u->v),将v的入度+1,注意去重:即同一条边(u->v)出现多次,只算一次)。 步骤2:统计入度为0的节点个数,记为zeroInDegreeCount。 - 如果zeroInDegreeCount != 1,则输出"no " + zeroInDegreeCount。 - 否则,继续检查:除了根节点(那个入度为0的节点)外,其他节点的入度是否都为1? 如果存在一个节点(非根)入度不为1,则输出"no " + zeroInDegreeCount(注意:此时zeroInDegreeCount是1,但是题目要求输出的是入度为0的节点个数,所以这里应该是输出"no 1"吗?但是题目要求输出的是入度为0的节点个数,所以即使这里条件不满足,我们输出的还是1?不对,题目要求输出的是入度为0的节点个数,所以即使条件2不满足,我们输出的k还是1?但是题目要求是输出入度为0的节点个数,所以不管条件2满不满足,只要入度为0的节点个数是1,那么输出no时k就是1?这样不对,因为题目要求是:如果不是,输出"no k",其中k是入度为0的节点个数。所以即使条件2不满足(即存在非根节点入度不为1),我们输出的k也是1(因为入度为0的节点个数为1)。但是,题目并没有说这种情况,所以我们需要重新审视题目要求。 重新读题:题目要求输出:如果是,输出"yes root";如果不是,输出"no k",其中k是入度为0的节点个数。 所以,无论什么原因导致不是,我们都输出no,然后k是入度为0的节点个数(即zeroInDegreeCount)。 因此,我们可以这样: 1. 统计入度为0的节点个数,记为k。 2. 如果k不等于1,则直接输出"no k"。 3. 如果k等于1,那么再检查其他节点入度是否都是1(除了根节点)。如果存在一个节点(非根)入度不为1(可能是0或者大于1),则输出"no 1"(因为入度为0的节点个数是1)。 4. 如果满足条件1和条件2,那么从根节点(那个入度为0的节点)开始进行BFS/DFS遍历,记录访问到的节点数。如果访问的节点数等于n,那么就是,输出"yes root";否则,输出"no 1"(因为不连通,所以不是,但入度为0的节点个数还是1)。 注意:条件2(除根节点外入度都是1)和条件3(连通)是独立的。条件2不满足时,我们直接输出"no 1";条件2满足但条件3不满足,也输出"no 1"。 但是,条件2满足时,边的数量一定是n-1吗?因为每个节点(除根)有一条入边,所以总入边数为n-1。而图中没有重复边(已经去重),所以总边数就是n-1。但是,这并不能保证连通(因为可能有两个连通分量,其中一个连通分量是一棵,另一个是一个环?但是条件2要求每个节点入度为1或0(根),所以不可能出现环(因为环上节点入度至少为1,但环上节点如果有多个入边?实际上,条件2已经限制了每个节点只能有一个父节点,所以整个图只能是一个形结构,但是可能不连通?)注意:条件2只限制了每个节点入度为1(除了根节点),但是并没有限制连通性。所以有可能存在多个(即森林)?但是,如果存在多个,那么每个都有一个根节点(入度为0),那么入度为0的节点个数k就会大于1,而我们在第一步就已经判断了k必须为1。所以,如果k=1,那么整个图中只有一个根节点,其他节点入度都是1,那么整个图一定是连通的吗? 证明:假设有n个节点,只有一个根节点(入度为0),其他节点入度为1,那么从根节点出发,沿着出边可以到达它的子节点,子节点再到达下一层,直到所有节点。因为每个节点只能有一个父节点(入度为1),所以不可能出现环(否则环上节点会有多个父节点?),也不可能出现两个独立的(因为独立的会有两个根节点,即两个入度为0的节点)。所以,整个图一定是连通的,且是一棵。 因此,实际上,当满足: k=1(只有一个入度为0的节点) 其他节点入度都为1 那么,这个图一定是一棵(连通且无环)。 所以,我们只需要检查: 1. 入度为0的节点个数为1。 2. 其他节点入度均为1。 满足这两个条件,则一定是。 但是,我们还需要注意:节点编号从1到n,但输入中可能没有出现所有的节点?题目说“Hence we assume that all the nodes are numbered from 1 to n”,所以节点就是1到n。所以,我们统计入度数组时,要初始化所有节点(1~n)的入度为0。 另外,去重:我们可以用set或者map来记录边是否重复。例如,用set<pair<int,int>>来存储边(u,v),如果已经存在,则不再增加入度。 步骤: 1. 读入n。 2. 初始化一个入度数组indegree,大小为n+1(下标1~n),初始化为0。 3. 初始化一个边集合edgeSet(用于去重)。 4. 初始化邻接表graph(用于后续遍历?实际上,我们不需要遍历?因为我们已经不需要检查连通性了?因为根据上面的证明,只要满足条件1和2,就一定是连通且无环的。所以我们可以不用遍历?但是,题目要求输出根节点,所以我们需要知道根节点是哪个(即那个入度为0的节点)。所以,我们只需要记录入度,然后找到根节点即可。 但是,我们还需要注意:有可能存在自环?题目保证source不等于destination,所以没有自环。 所以,算法: while (输入) { 读入u, v; 如果u==0,则跳出循环; 如果边(u,v)没有在edgeSet中出现过,则: indegree[v]++; // 因为是有向边u->v 将(u,v)加入edgeSet; // 注意:邻接表在这里不需要建立?因为我们不需要遍历?但是为了证明连通性,我们可能需要遍历?但是根据上面的证明,我们不需要遍历。所以我们可以不建图。 } 但是,我们还需要考虑:条件2(其他节点入度为1)和条件1(根节点入度为0)是否已经足够?是的,所以不需要建图遍历。 然而,我们还需要考虑:是否存在孤立节点?即节点没有出现在任何边中。那么这样的节点入度为0。所以,在统计入度为0的节点个数时,这些节点也会被计入。因此,如果存在一个节点没有出现在任何边中(即没有入边也没有出边?但题目输入只给出了有向边,所以节点可能没有入边,也没有出边(即孤立节点)),那么它的入度就是0。所以,我们统计入度为0的节点个数时,孤立节点也会被算入。 例如:n=3,输入边: 1->2 0 那么节点3的入度就是0。所以入度为0的节点有两个:1(因为1没有入边,但1有出边,所以它入度是0?)和3(入度为0)。所以k=2,输出"no 2"。 因此,我们的算法是: 1. 初始化indegree[1..n]=0。 2. 用set记录边去重。 3. 读入边,直到u==0为止。对于每条边(u,v): if (edgeSet.find({u,v}) == edgeSet.end()) { indegree[v]++; edgeSet.insert({u,v}); } 4. 注意:我们只记录入度,没有记录出度。因为出度不影响的条件(的条件只关心入度,以及根节点)。 5. 统计入度为0的节点个数k,并记录根节点(如果k==1,则找到那个入度为0的节点作为根)。 6. 检查:除了根节点外,其他节点的入度是否都是1?注意:可能有节点没有出现在边中(即入度为0,且不是根节点?因为根节点只有一个,所以其他入度为0的节点都是孤立节点,这些节点显然不满足入度为1(因为入度是0,不是1))。所以,在k==1的情况下,我们还要检查其他节点(非根节点)的入度是否都是1。如果存在一个节点(非根)入度不为1(可能是0或者大于1),那么就不满足条件。 所以,具体代码: int k = 0; int root = -1; for (int i=1; i<=n; i++) { if (indegree[i] == 0) { k++; root = i; // 记录最后一个入度为0的节点,但k>1时root没有意义,我们只关心k==1的情况 } } if (k != 1) { cout << "no " << k << endl; } else { // 检查除了root节点外,其他所有节点的入度是否都是1 bool flag = true; for (int i=1; i<=n; i++) { if (i == root) continue; if (indegree[i] != 1) { flag = false; break; } } if (flag) { cout << "yes " << root << endl; } else { cout << "no 1" << endl; // 因为入度为0的节点个数是1,所以输出"no 1" } } 但是,这里有一个问题:我们只统计了入度,但是节点可能没有出边,但中非叶子节点应该有出边。然而,的条件中并没有要求出边,所以一个节点出度为0(叶子节点)是允许的。所以我们的算法没有问题。 但是,我们考虑一个反例:n=3,边为:1->2, 3->2,那么节点2的入度为2(来自1和3),节点1和3的入度都是0。所以k=2,输出"no 2"。正确。 另一个反例:n=3,边为:1->2, 1->3,那么节点1的入度为0,节点2和3的入度为1。满足条件,输出"yes 1"。正确。 再一个反例:n=3,边为:1->2, 2->3, 3->1,那么节点1、2、3的入度都是1(注意没有入度为0的节点),所以k=0,输出"no 0"。正确。 但是,注意:节点1、2、3的入度都是1,但是形成了环,且没有根节点(入度为0),所以k=0,输出"no 0"。 还有一个情况:n=3,没有边。那么三个节点入度都是0,所以k=3,输出"no 3"。 因此,代码实现: 注意:题目中节点编号从1到n,所以我们需要考虑所有节点。 但是,我们只记录了有边的目标节点的入度,而源节点呢?源节点的入度可能为0(如果没有其他边指向它),也可能不为0。所以我们的算法中,初始化所有节点的入度为0,然后对于每条边(u->v),增加v的入度。所以源节点u的入度没有被这条边影响(除非有其他边指向u)。 所以,算法正确。 但是,题目中可能出现节点没有在边中出现的情况(即没有作为源节点也没有作为目标节点),那么它的入度就是0(初始化为0,且没有边指向它)。 因此,我们按照上面的步骤写代码即可。 但是,题目要求:重复边只算一条。所以我们需要用set去重。注意:set中存储pair<int,int>,默认支持。 代码: 注意:输入以0结束,所以当读到u为0时停止。 由于节点数n最大为10000,边数最多10000,所以set去重是可以的(最多10000条边,插入和查找都是O(1))。 具体代码: 步骤: int n; cin >> n; vector<int> indegree(n+1, 0); set<pair<int, int>> edgeSet; int u, v; while (cin >> u && u != 0) { cin >> v; // 如果u和v在[1,n]范围内?题目保证节点编号1~n,且u不等于v,且u不为0(此时u==0会跳出) if (edgeSet.find({u, v}) == edgeSet.end()) { edgeSet.insert({u, v}); indegree[v]++; // 目标节点v的入度增加 } } 然后统计入度为0的节点个数k,以及记录根节点(当k==1时)。 但是,注意:有可能一条边重复出现多次,我们只记录一次,所以不会重复增加入度。 然后按照上面的逻辑输出。 但是,我们考虑一个边界:n=1(只有一个节点)。那么,这个节点应该是根节点(入度为0)。如果没有任何边(因为只有一个节点,所以边也只能是0条),那么k=1(这个节点入度为0),然后检查其他节点(没有其他节点)所以满足条件,输出"yes 1"。 如果有一条边:1->1?但是题目保证source不等于destination,所以不可能出现。所以n=1时,只能没有边。 因此,代码可以处理n=1的情况。 但是,我们再看题目:输入以0结束,所以当n=1时,输入只有0,那么不会进入循环,所以indegree[1]初始为0,然后k=1,且没有其他节点(所以循环检查其他节点时跳过root,其他节点不存在),所以flag为true,输出"yes 1"。 所以,代码正确。 但是,题目中还有一个要求:输出时,如果是,输出"yes root",root是根节点的编号;如果不是,输出"no k"(k是入度为0的节点个数)。 因此,我们按照上述思路编写代码。 注意:题目要求代码不要有注释,所以下面代码去掉注释。 但是,题目要求使用C++,且不要有注释。 代码实现(无注释):</think>根据问题描述,需要判断一个有向图是否是一棵的条件包括: 1. 只有一个入度为0的节点(根节点)。 2. 除根节点外,其他所有节点的入度均为1。 3. 图中不存在重复边(输入中重复边视为一条)。 ### 解决方案 1. **初始化**:读取节点数 `n`,初始化入度数组 `indegree`(大小为 `n+1`,索引1~n),使用集合 `edgeSet` 记录边以实现去重。 2. **处理输入**:读取每条边,若边未出现过,则增加目标节点的入度,并将边加入集合。 3. **统计入度为0的节点**:遍历所有节点,统计入度为0的节点数 `k`。 4. **判断是否**: - 若 `k != 1`,输出 `no k`。 - 若 `k == 1`,检查除根节点外其他节点入度是否均为1。若是则输出 `yes root`;否则输出 `no 1`。 ### C++代码实现 ```cpp #include <iostream> #include <vector> #include <set> using namespace std; int main() { int n; cin >> n; vector<int> indegree(n + 1, 0); set<pair<int, int>> edgeSet; int u, v; while (cin >> u && u != 0) { cin >> v; if (edgeSet.find({u, v}) == edgeSet.end()) { edgeSet.insert({u, v}); indegree[v]++; } } int k = 0; int root = -1; for (int i = 1; i <= n; i++) { if (indegree[i] == 0) { k++; root = i; } } if (k != 1) { cout << "no " << k << endl; } else { bool flag = true; for (int i = 1; i <= n; i++) { if (i == root) continue; if (indegree[i] != 1) { flag = false; break; } } if (flag) { cout << "yes " << root << endl; } else { cout << "no 1" << endl; } } return 0; } ``` ### 代码解释 1. **初始化**:`indegree` 数组记录每个节点的入度,初始为0;`edgeSet` 存储已处理的边。 2. **输入处理**:读取每条边,若边未重复,则更新目标节点的入度。 3. **统计根节点**:遍历节点,统计入度为0的节点数 `k` 并记录根节点 `root`。 4. **条件判断**: - `k != 1`:存在多个根或无根节点,输出 `no k`。 - `k == 1`:检查其他节点入度是否均为1。若是则输出 `yes root`;否则输出 `no 1`。 ### 样例验证 - **Sample Input 1**:根节点6满足条件,输出 `yes 6`。 - **Sample Input 2**:节点1和3入度为0(`k=2`),输出 `no 2`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值