ZOJ1097-Code the Tree(集合+优先队列+图)

1 篇文章 0 订阅
1 篇文章 0 订阅

题意

给出了一个树(即没有循环的连通图),其顶点由整数1、2,…,n编号。这样的树的“ Prufer”代码构建如下:提取具有最小数目的叶子(仅入射到一个边缘的顶点)。将该叶子及其入射边缘从图形中删除,同时将与该叶子相邻的顶点的数目记下来。在获得的图形中,重复此过程,直到只剩下一个顶点(顺便说一句,该顶点始终具有数字n)。写下的n-1个数字的序列称为树的Prufer码。
给定一棵树,您的任务将计算其Prufer代码。用以下语法指定的语言单词来表示树:

T ::= “(” N S “)”
S ::= " " T S
| empty
N ::= number

也就是说,树周围有括号,并有一个数字表示根顶点的标识符,然后是由单个空格字符分隔的任意多个(可能没有)子树。例如,看一下下图中的树,该树在样本输入的第一行中表示。

注意,根据以上给出的定义,树的根也可以是叶。仅为了便于说明,我们将某些顶点指定为根。通常,我们在这里处理的被称为“无根树”。
例图

输入

输入包含几个测试用例。每个测试用例在输入文件的一行上指定一棵如上所述的树。输入由EOF终止。您可以假设1 <= n <= 50。

输出

为每个测试用例生成一行,其中包含指定树的Prufer代码。用一个空格分隔数字。不要在行尾打印任何空格。

样例

Sample Input
(2 (6 (7)) (3) (5 (1) (4)) (8))
(1 (2 (3)))
(6 (1 (4)) (2 (3) (5)))

Sample Output
5 2 5 2 6 2 8
2 3
2 1 6 2 6

解题思路

处理输入

由题意和样例一我们可以知道,所给树的根节点,也是可以被当作叶子节点处理的,同时,我们需要知道的是每一个结点的度即可,并不需要知道结点位于树的位置。所以,我们本题应当将之当作一个无向无环图来处理而不是树。那么,处理时,我们只要记录图中具有了哪些边即可。

本题的输入是每一个括号表示一棵树,括号内首先由一个数字表示结点编号,若该树由子树(或子结点)存在,则在结点编号空格后,同样以括号表示一棵树。

那么这道题便可采用递归获取所有边,不过在本题中,遇到右括号后,就代表一棵树的结束,也就是说,这颗树对应的编号将不会再用到,且这颗树的所有子树都会在该右括号之前列出,于是我便采用数组记录每一层树的编号,以样例一为例,我所开设的数组将会记录如下:

下标012
可能记录的编号26 3 5 87 1 4

即树的同一层内的所有结点都由一个数组内一个元素表示,这是由于本题输入的特殊性,使得当遇到新的同层结点时,必然是在原本的同层结点已经使用完毕以后,那么我们便可无所顾虑的将之覆盖。而通过所记录的编号,可以轻易知晓这个结点所相连的另一个结点的编号,并用集合数组记录每一个结点所相连的结点都有哪些。

寻找仅有一个度的最小结点

我们利用集合的 size() 方法可以轻易知晓每个结点的度,那么我们只要将所有度为1的结点装入优先队列里,便可快速从中拿出度为1的最小结点 A。将这个结点 A 相连的结点 B 的编号输出,再将结点 B 集合内的 A 删除即完成了一次删边操作,若删除后结点 B 的度剩余1,那么就将结点 B入队。

注:本题有点卡常数

AC代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<queue>
#include<set>
using namespace std;
priority_queue<int>Q;//优先队列,用于快速取出度为1的最小结点
const int maxn = 105;
set<int>S[maxn];//存放每一个结点的相连结点
int n = 0;//结点总数
int floors;//当前结点所在的层数
void init() {
	for (int i = 0; i < maxn; i++)
		S[i].clear();
	while (!Q.empty())
		Q.pop();
	n = 0;
	floors = 0;
}
int main() {
	char str;
	int t[maxn];//记录每一层当前的编号(遇到同层结点则被覆盖)
	while (scanf("%c", &str) != EOF) {
		init();
		//处理输入
		while (str != '\n') {
			if (str == '(')
				floors++;//左右括号决定树层数的增减
			else if (str == ')')
				floors--;
			else if (str >= '0' && str <= '9') {
				int a = str - '0'; // 结点编号
				while (true) {
					scanf("%c", &str);
					if (str >= '0' && str <= '9')
						a = a * 10 + str - '0';
					else
						break;
				}
				n++;
				t[floors] = a;//将当前层数的结点编号覆盖
				if (floors > 1) {//若层数大于1,则与上一次的结点构成边
					S[a].insert(t[floors - 1]);
					S[t[floors - 1]].insert(a);
				}
				continue;
			}
			scanf("%c", &str);
		}

		//将度为1的结点入队
		for (int i = 1; i <= n; i++) {
			if (S[i].size() == 1)
				Q.push(-i);
			//将所有度为1的结点入队,由于我采用的是最大堆优先队列,为了能构成最小堆效果,故将编号的相反数入库
		}

		//开始计算Prufer序列
		for (int k = 1; k < n; k++) {
			int now = -Q.top();
			Q.pop();
			int b = *S[now].begin();//由于只有一个元素,则不使用迭代器,可直接利用begin找到元素所在位置
			S[b].erase(now);//将结点b里的now元素删除

			cout << b;
			if (k < n - 1)
				cout << " ";

			if (S[b].size() == 1)
				Q.push(-b); //若度为1,入队
		}
		cout << endl;

	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这是一个使用优先队列(priority queue)的题目,可以使用C++ STL中的优先队列来实现。 以下是样例代码: ```c++ #include <iostream> #include <queue> // 包含 priority_queue 头文件 using namespace std; int main() { int n; while (cin >> n) { priority_queue<int, vector<int>, greater<int>> pq; // 定义小根堆 for (int i = 0; i < n; i++) { int x; cin >> x; pq.push(x); // 将元素加入优先队列 } int ans = 0; while (pq.size() > 1) { // 只要队列中还有两个及以上元素 int a = pq.top(); pq.pop(); int b = pq.top(); pq.pop(); ans += a + b; pq.push(a + b); // 新元素入队 } cout << ans << endl; } return 0; } ``` 在这个代码中,我们使用了C++ STL中的优先队列 `priority_queue`,它有三个模板参数: - `int`:表示队列中存储的元素类型是 `int`。 - `vector<int>`:表示队列内部使用 `vector` 作为基础容器。 - `greater<int>`:表示使用小根堆来存储元素。 在主函数中,我们首先读入元素,然后将它们加入到优先队列中。接着,我们依次取出队列中的两个最小元素,将它们相加并累加到答案中,然后将它们的和作为新元素加入到队列中。最后,当队列中只剩下一个元素时,输出答案。 需要注意的是,在使用小根堆时,我们要将模板参数 `greater<int>` 作为第三个参数传递给 `priority_queue`。如果不指定第三个参数,则默认使用大根堆。 另外,这里使用了 `while (cin >> n)` 的方式来不断读入测试用例,直到遇到输入结束符为止(比如EOF或者Ctrl+Z)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值