7月28号暑假集训 二叉树

题目链接:二叉树

A:
题目要求:说来说去就是后序遍历转层序遍历
思路:每碰到一个小写字母,那么这个小写字母一定是叶子节点,那么用数组模拟指针,开辟一个位置,这个位置放置这个小写字母,并且挂上小写字母的信息(左儿子,右儿子,id),挂上id这个信息便于他的父亲指向他,而叶子节点的左儿子、右儿子均为-1。如果碰到一个大写字母,大写字母一定是非叶子节点,那么大写字母一定是前面两坨东西的父亲(有可能是另一个大写字母带着一堆儿子,也可以是单独的小写字母),无论如何,前面两个字母都是它的儿子,那么思路就很清晰了,直接用一个栈来存储顺序,小写字母就存了然后扔进去,大写字母就存了以后把栈顶两个取出来当儿子,自己再进去,这样一直操作下去,最终会有一个大写字母作为根节点的,然后问题就解决了,就通过这种方法将这个二叉树构建出来了。
如图,一路从叶子节点向上构造,给出样例1的二叉树构造图形
在这里插入图片描述

之后就很简单了。
只要通过bfs来进行层序遍历即可
印象中上学期看的《啊哈!算法》中有详细生动的关于二叉树的介绍。
二叉树遍历方式如下:
dfs:前序遍历、中序遍历、后序遍历
bfs:层序遍历

不会吧不会真的有人不会bfs吧!

层序遍历(一层一层的遍历)

用队列来实现对二叉树的bfs
最后再对字符串进行翻转输出,大功告成!

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
#define ll long long
char s[10010];
struct node
{
	char c;
	int l, r, id;
	node() { l = -1, r = -1; }
}tree[10010];
stack <node> st;
int id, root;
queue <node> q;
char ans[10010];
int cnt;
void bfs()
{
	cnt = 0;
	q.push(tree[root]);
	while (q.size())
	{
		node u = q.front();
		q.pop();
		ans[cnt++] = u.c;
		if(u.l!=-1) q.push(tree[u.l]);
		if(u.r!=-1) q.push(tree[u.r]);
	}
	for (int i = cnt - 1; i >= 0; i--) printf("%c", ans[i]);
	printf("\n");
}
int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		while (q.size()) q.pop();
		while (st.size()) st.pop();
		scanf("%s", &s);
		id = 0;
		int len = strlen(s);
		for (int i = 0; i < len; i++)
		{
			if (s[i] >= 'a' && s[i] <= 'z')//小写字母
			{
				tree[id].c = s[i];	
				tree[id].id = id;
				tree[id].l = -1;
				tree[id].r = -1;
				st.push(tree[id]);
				id++;
			}
			else if(s[i] >= 'A' && s[i] <= 'Z')//大写字母
			{
				node tmp1 = st.top(); st.pop();
				node tmp2 = st.top(); st.pop();
				tree[id].c = s[i];
				tree[id].r = tmp1.id;
				tree[id].l = tmp2.id;
				tree[id].id = id;
				st.push(tree[id]);
				id++;
			}
		}
		root = id - 1;//根节点编号为id-1
		bfs();
	}

}

B:
题目要求:求出n个节点的二叉树中,有多少种节点数不同的子树
思路:
对于完美二叉树,求它的最大深度
对于非完美二叉树,求他的个数
最终加起来得到答案

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define ll long long
ll N;
ll ans;
ll ansdep;
void find(ll x)
{
	ll dep = 1;
	ll l = x;
	ll r = x;
	while ((l << 1) <= N)
	{
		l <<= 1;
		dep++;
	}
	while ((r << 1 | 1) <= N)
	{
		r = r << 1 | 1;
	}
	if (l <= r)
	{
		if (ansdep < dep) ansdep = dep;
	}
	else
	{
		find(x << 1);
		find(x << 1 | 1);
		ans++;
	}
}
int main()
{
	while (scanf("%lld", &N)!=EOF)
	{
		ans = 0;
		ansdep = 0;
		find(1);
		printf("%lld\n", ans + ansdep);
	}

}

C:
紫书P148原题
思路:直接模拟必然TLE,求目标球走的位置即可
每个小球都会落在根节点上,因此前两个小球必然是一个在左子树一个在右子树,所以只用看小球是第几个落在左子树的,就能知道小球下一步的方向。

#include <iostream>
using namespace std;
int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		int D, I;
		scanf("%d%d", &D, &I);
		long long ans = 1;
		for (int i = 1; i < D; i++)
		{
			if (I & 1)
			{
				I = (I + 1)>>1;
				ans <<= 1;
			}
			else
			{
				I = I >> 1;
				ans = (ans << 1) + 1;
			}
		}
		printf("%lld\n", ans);
	}
	scanf("%d", &T);
	return 0;
}

D:
紫书P176原题
思路:构建二叉树,模拟01串二进制求解

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
char ans[140];
int quan[10];
char chery[10];
vector <char> res;
int main()
{
	int t;
	int kase = 0;
	while (scanf("%d", &t)&&t) {
		res.clear();
		for (int i = 1; i <= t; i++)
		{
			int num;
			char x;
			cin >> x >> quan[i];
		}
		cin >> ans + 1;
		int m;
		scanf("%d", &m);
		while (m--)
		{
			cin >> chery + 1;
			int q = 1 << (t - 1);
			int p = 1;
			for (int i = 1; i <= t; i++, q >>= 1)
			{
				if (chery[quan[i]] == '1')
					p += q;
			}
			//printf("%d", p);
			//printf("%c", ans[p]);
			res.push_back(ans[p]);
		}
		printf("S-Tree #%d:\n", ++kase);
		for (int i = 0; i < res.size(); i++)
			printf("%c", res[i]);
		printf("\n\n");
	}
}

E:
紫书P155原题
思路:用中序遍历和后序遍历构建二叉树,再进行递归遍历求解。

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 10010;
vector <int> in;
vector <int> post;
int n = 0;
int minsum=1<<30, ans = -1;
struct Node
{
	int v;
	Node* left=NULL, * right=NULL;
};
Node* createtree(int i1,int j1,int i2,int j2)
{
	//i1和j1指针管理中序遍历,i2和j2指针管理后序遍历
	if (i1 >= j1 || i2 >= j2) return NULL;
	Node* root = new Node;
	root->v = post[j2 - 1];
	int j = find(in.begin() + i1, in.begin() + j1, post[j2 - 1]) - in.begin();
	root->left = createtree(i1, j, i2, i2 + (j - i1));
	root->right = createtree(j + 1, j1, i2 + (j - i1), j2 - 1);
	return root;
}
void dfs(Node *root,int sum)
{
	if (root->left == NULL && root->right == NULL)
	{
		sum += root->v;
		if (sum < minsum || (sum == minsum && (root->v < ans)))
		{
			minsum = sum;
			ans = root->v;
		}
		return;
	}
	if (root->left != NULL)
	{
		dfs(root->left, sum + root->v);
	}
	if (root->right != NULL)
	{
		dfs(root->right, sum + root->v);
	}
}
int main()
{
	string line_in;
	string line_post;
	while (getline(cin, line_in))
	{
		in.clear();
		post.clear();
		getline(cin, line_post);
		//cout << line_in << endl;
		//cout << line_post << endl;
		stringstream ss_in(line_in);
		stringstream ss_post(line_post);
		int x_in;
		int x_post;
		while (ss_in >> x_in) {
			ss_post >> x_post;
			in.push_back(x_in);
			post.push_back(x_post);
		}
		/*for (int i = 0; i < n; i++)
		{
			cout << in[i] << ' ';
		}
		cout << endl;
		for (int i = 0; i < n; i++)
		{
			cout << post[i] << ' ';
		}
		cout << endl;*/
		minsum = 1<<30;
		ans = -1;//初始化两个值
		Node* ntree=createtree(0, in.size(), 0, post.size());
		dfs(ntree, 0);
		printf("%d\n", ans);
	}
}

F:
思路:二叉搜索树的构建,按右子树、左子树、根顺序输出

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
#define ll long long
int a[3030];
void dfs(int cur,int n)
{
	int root = a[n];
	int r;
	int i;
	//cout << cur << " " << n << endl;
	if (cur == n)
	{
		printf("%d\n", a[cur]);
		return;
	}
	for (i = cur; i <= n; i++)
	{
		if (a[i] >= root)
		{
			r = a[i];
			break;
		}
	}
	if(i<=n-1) dfs(i, n - 1);
	if(cur<=i-1) dfs(cur, i - 1);
	printf("%d\n", root);
}
int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	dfs(1, n);
}

H:
思路:给出叶子节点,找出规律进行回溯,(第一发直接模拟TLE了),需要再找一个规律,当连续往同一个方向回溯的时候,可以连着跳(省效率),比如连续n次从左孩子跳回父亲。
具体上代码:

TLE代码:

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
#define ll long long
int main()
{
	int t;
	scanf("%d", &t);
	int kase = 0;
	while (t--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		int left = 0, right = 0;
		while (!(a == 1 && b == 1))
		{
			if (a < b)
			{
				right++;
				b = b - a;
			}
			else
			{
				left++;
				a = a - b;
			}
		}
		printf("Scenario #%d:", ++kase);
		printf("%d %d\n\n", left, right);
	}
}

显然,一步一步的找回去是不行的,比如说(210e9,1)这个数据,我们需要操作210e9次,直接T掉,能够连续跳的连续跳,我就想到了以下的方式。

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
#define ll long long
int main()
{
	int t;
	scanf("%d", &t);
	int kase = 0;
	while (t--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		int left = 0, right = 0;
		while (a != 1 && b != 1)
		{
			if (a < b)
			{
				right += b / a;
				b = b % a;
			}
			else
			{
				left += a / b;
				a = a % b;
			}
		}
		right += b - 1;
		left += a - 1;
		printf("Scenario #%d:\n", ++kase);
		printf("%d %d\n\n", left, right);
	}
}

K:
思路:给出这样的题面,显然是需要枚举来构造答案,然而,n==1e5,如果枚举两个数,那必定超时。只需要枚举一个数i,通过A,是可以算出原本需要枚举的另一个数j的范围,j1<=i/A<=j2,其他的j是肯定不合理的。
那么只需要枚举结束,便可以得到最终答案。
取绝对值要用fabs不要用abs!!!一定会wa掉

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
#include <cmath>
using namespace std;
#define ll long long
int main()
{
	double A;
	int L;
	scanf("%lf%d", &A, &L);
	int ans1, ans2;
	double ans = 999999;
	for (int i = 1; i <= L; i++)
	{
		double j = i * 1.0 / A;
		int j1 = (int)j;
		int j2 = j1 + 1;
		if (fabs(i * 1.0 / j1 - A) < ans) {
			ans = fabs(i * 1.0 / j1 - A); ans1 = i, ans2 = j1;
		}
		if (fabs(i * 1.0 / j2 - A) < ans) {
			ans = fabs(i * 1.0 / j2 - A); ans1 = i, ans2 = j2;
		}
	}
	printf("%d %d\n", ans1, ans2);
}

精力有限,今天就写这么多吧,以后有机会再写(咕咕咕)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值