Part 8.3.1 二叉树

题单

二叉树参考学习资料

P1087 [NOIP2004 普及组] FBI 树

思路

唯一一个难点是如何判断下标从l到r的串是F串还是B串还是I串。如果是一一枚举去判断那么时间复杂度最坏达到O(n^2),其中n = 2 ^ N。因为N <= 10,所以这个题也可以做。那如果把n放大到1e6呢?显然暴力枚举百年不可行了。于是我们想要O(1)得到是哪种串,不难想到用前缀和(具体见代码)。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1100;
int N, n, pre[maxn];
char s[maxn];
struct worker 
{
	int l, r;
	char ans;
} t[maxn << 1];
void build (int i, int l, int r) 
{
	if (pre[r] - pre[l - 1] == 0)
		t[i].ans = 'B';
	else if (pre[r] - pre[l - 1] == r - l + 1)
		t[i].ans = 'I';
	else t[i].ans = 'F';
	if (l == r) return;
	t[i].l = i << 1, t[i].r = i << 1 | 1; 
	int mid = (l + r) >> 1;
	build (i << 1, l, mid), build (i << 1 | 1, mid + 1, r);
}
void order (int i) 
{
	if (t[i].l == 0) 
	{
		printf ("%c", t[i].ans);
		return;
	}
	order (t[i].l), order (t[i].r);
	printf ("%c", t[i].ans);
}
int main ()
{
	scanf ("%d", &N);
	n = 1;
	for (int i = 1; i <= N; i++)
		n *= 2;
	cin >> s;
	for (int i = 0; i < n; i++) 
		pre[i + 1] = pre[i] + (s[i] - '0');
	build (1, 1, n);
	order (1);
	return 0;
}

收获

学习了前序遍历、中序遍历和后序遍历。
想到了用可以前缀和优化
知道了建树的两种方法(int类型的build函数和void类型的build函数两种方法)

P1030 [NOIP2001 普及组] 求先序排列

思路

后序排列有一个特点:这个子树的根节点一定是排列的最后一个点,整个排列的顺序是根节点的左子树的后序排列后紧接着右子树的后序排列最后是根节点;中序排列有个特点:以这棵子树的根节点为界,其左侧为这棵子树根节点的左子树的中序排列,其右侧为这棵子树根节点的右子树的中序排列。为此,如果我们想要建树,在已知某个树的中序排列和后序排列的情况下,我们可以先通过后序排列找到根节点,再找到根节点在中序遍历中的位置,进而找到左子树的中序排列和右子树的中序排列,进而得到左子树的点数和右子树的点数,进而得到左子树和右子树的后序排列,于是对左子树和右子树进行递归建子树这里有一个重点Δ,就是在进行递归前,要判断左子树或右子树是否存在!!! 对于,剩下的前序遍历就是不说了)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 15;
int len;
char zpts[maxn], hpts[maxn];
int find (char ch) 
{
	for (int i = 0; i <= len - 1; i++)
		if (ch == zpts[i]) return i;
}
void dfs (int l, int r, int li, int ri) // 前为中序,后后序 
{
	if (ri == li) 
	{
		printf ("%c", hpts[ri]);
		return;
	}
	int id = find (hpts[ri]);
	printf ("%c", hpts[ri]);
	if (l < id) dfs (l, id - 1, li, li + id - l - 1); //判断条件用手划拉一下就知道了
	if (r > id) dfs (id + 1, r, ri - r + id, ri - 1);
	return;
}
int main ()
{
	cin >> zpts;
	cin >> hpts;
	len = strlen (zpts);
	dfs (0, len - 1, 0, len - 1);
	return 0;
}

收获

知道了:已知树的中序遍历和后序遍历可以唯一确定树;已知树的前序遍历和中序遍历也可以唯一确定树;但已知树的前序遍历和后序遍历,由于中序遍历有多种,故不可以唯一确定树。

P1305 新二叉树

思路

水题,需要注意的点是:要在左子树或右子树存在的情况下再去递归!另一个需要注意的点在代码中说

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10000;
int n;
char s[maxn], real_r;
struct root // 某个字母的左右儿子是什么 
{
	char l, r;
} r[maxn];
struct worker 
{
	int l, r;
	char chi;
} t[maxn << 1]; 
void build (char root, int i) 
{
	t[i].chi = root;
	if (r[root - 'A'].l != '*') t[i].l = i << 1, build (r[root - 'A'].l, i << 1);
	if (r[root - 'A'].r != '*') t[i].r = i << 1 | 1, build (r[root - 'A'].r, i << 1 | 1);
	return;
}
void dfs (int i) 
{
	printf ("%c", t[i].chi);
	if (t[i].l) dfs (i << 1);
	if (t[i].r) dfs (i << 1 | 1);
	return;
}
int main ()
{
	scanf ("%d", &n);
	for (int i = 1; i <= n; i ++) 
	{
		cin >> s;
		if (!real_r) real_r = s[0];
		r[s[0] - 'A'].l = s[1]; // 写s[0] - 'A'或直接int (s[0]);但不能s[0] - 'a',因为有可能s[0]是大写某字母,导致IndexError.评价是不如直接int (s[0])
		r[s[0] - 'A'].r = s[2];
	}
	build (real_r, 1);
	dfs (1);
	return 0;
}

收获

原来空字符串在C++中返回False啊,好耶!

P1229 遍历问题

思路

首先,我们可以知道如果一个节点既有左儿子又有右儿子,那么这三个点之间在树中的位置关系是确定的,若其在前序遍历中为ABC,则在后序遍历中为BCA;如果一个节点是叶子节点,那么这个点也不会对我们最后的答案造成影响,若在在前序遍历中为A,则在后序遍历中亦为A;但是,如果一个节点只有一个儿子,因为不确定这个儿子是左儿子还是右儿子,所以在这里会造成中序遍历的多种情况,若在前序遍历中为AB,则其在后序遍历中为BA由于对于上述三种情形,其在前序遍历中的串与在后序遍历中的对应串的对应方式互不相同;又由于我们的输入是前序遍历和后序遍历,故若在前序遍历中有某一字串AB满足根节点只有一个儿子的情形,则在后序遍历中有且只有一个BA串与之对应。因此,我们只需要找到所有的满足在前序AB在后序BA的AB串有多个就行了,记为tot;则答案为1 << tot。时间复杂度为O(lens^2)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
int ans;
char si[maxn], sii[maxn];
int main ()
{
	cin >> si >> sii;
	int lens = strlen (si);
	for (int i = 0; i < lens - 1; i++) 
		for (int j = 0; j < lens - 1; j++) 
			if (si[i] == sii[j + 1] && si[i + 1] == sii[j]) ans ++;
	printf ("%d", 1 << ans); 
	return 0;
}

收获

知道了在已知前序遍历和后续遍历的情形下,如何求中序遍历的可能排列数

P5018 [NOIP2018 普及组] 对称二叉树

思路

唯一的难点是如何判断某个节点的左右两棵子树是否是镜像对称的。如果对于每个节点,对于他的左子树和右子树(因为探讨没有左子树或右子树的节点在初始化ans = 1的前提下是没有意义的),如果暴力去搜索判断的化有很大的可能会超时!因为n <= 1e6,那么我们需要去寻找一种O(1)的方式去判断左右子树是否镜像对称那我们可以想到用一个数组hal(或har)[i]去储存一个数,而这个数唯一代表这个以i为根节点的子树从左向右(或从右向左)看得到的结构,换句话说,必须要保证对于不同的树结构,其对应的数一定要不同!那我们可以想到用hash去加密这个结构,此题屑用的是进制哈希和取余哈希

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int n;
// 进制hash
const long long basei = 999999751, baseii =  299999827, baseiii = 100000007;
const long long modi = 89999794200117649, modii = 999999786000011449;
long long w[maxn], ans = 1;
unsigned long long hal[maxn], Hal[maxn], har[maxn], Har[maxn];
//hal(r)为%modi得到得hash数组,Hal(r)是%modii得到得hash数组;如此这般可以存储得不同得树结构至少有modi * mdoii种!
struct point 
{
	long long l, r, value;
} t[maxn];
void dfs (long long i) 
{
	if (t[i].l) dfs (t[i].l);
	if (t[i].r) dfs (t[i].r);
	w[i] = 1 + w[t[i].l] + w[t[i].r];
	int l = t[i].l, r = t[i].r;
	// 对由左向右看得到的树进行加密的方式
	hal[i] = (basei * hal[l] + baseii * hal[r] + baseiii * t[i].value) % modi;
	Hal[i] = (basei * Hal[l] + baseii * Hal[r] + baseiii * t[i].value) % modii;
	// 把basei * hal[l] + baseii * hal[r]中的l和r互换,就是以相同的加密方式对对由右向左看得到的树进行加密
	har[i] = (basei * har[r] + baseii * har[l] + baseiii * t[i].value) % modi;
	Har[i] = (basei * Har[r] + baseii * Har[l] + baseiii * t[i].value) % modii;
	// hal[l] == har[r] && Hal[l] == Har[r]表示左子树从左往右看与右子树从右往左教看,结构一样得,因此他们镜像对称。
	if (w[l] == w[r] && hal[l] == har[r] && Hal[l] == Har[r]) ans = max (ans, w[i]);
	return;
}
int main ()
{
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++) 
		scanf ("%lld", &t[i].value);
	for (int i = 1; i <= n; i++)
		scanf ("%lld %lld", &t[i].l, &t[i].r),
		// 一点小细节
		t[i].l = (t[i].l == -1 ? 0 : t[i].l),
		t[i].r = (t[i].r == -1 ? 0 : t[i].r);
	dfs (1);
	printf ("%lld", ans);
	return 0;
}

收获

初步了解了进制hash和取余hash

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值