目录
1.P1827 [USACO3.4] 美国血统 American Heritage
作业
1.P1827 [USACO3.4] 美国血统 American Heritage
1.1题目
题目描述
农夫约翰非常认真地对待他的奶牛们的血统。然而他不是一个真正优秀的记帐员。他把他的奶牛 们的家谱作成二叉树,并且把二叉树以更线性的“树的中序遍历”和“树的前序遍历”的符号加以记录而 不是用图形的方法。
你的任务是在被给予奶牛家谱的“树中序遍历”和“树前序遍历”的符号后,创建奶牛家谱的“树的 后序遍历”的符号。每一头奶牛的姓名被译为一个唯一的字母。(你可能已经知道你可以在知道树的两 种遍历以后可以经常地重建这棵树。)显然,这里的树不会有多于 2626 个的顶点。
这是在样例输入和样例输出中的树的图形表达方式:
C
/ \
/ \
B G
/ \ /
A D H
/ \
E F
附注:
- 树的中序遍历是按照左子树,根,右子树的顺序访问节点;
- 树的前序遍历是按照根,左子树,右子树的顺序访问节点;
- 树的后序遍历是按照左子树,右子树,根的顺序访问节点。
输入格式
第一行一个字符串,表示该树的中序遍历。
第二行一个字符串,表示该树的前序遍历。
输出格式
单独的一行表示该树的后序遍历。
输入输出样例
输入 #1复制
ABEDFCHG CBADEFGH
输出 #1复制
AEFDBHGC
说明/提示
题目翻译来自NOCOW。
USACO Training Section 3.4
1.2思路
根据中序遍历和前序遍历,来递归划分左子树和右子树,再打印根结点以此来模拟后序遍历
1.3核心代码
递归:前序的第一个为根结点,找到根来划分中序遍历的左、右子树。
遍历左子树:前序遍历的左子树为去掉前一个数(为根结点),前序遍历左边界为l1+1;右边界为 左边界( l1 + 1)加上左子树的个数( i - l2+1)。 右子树的左边界l2,右边界结点 i -1。
遍历右子树:前序遍历左边界为左子树的右边界( l1 + i - l2 )+1;右边界为r1, 右子树的左边界 i + 1,右边界结点 r2 。
void PostOrder(int l1, int r1, int l2, int r2)//l1,r1为前序的左右边界,l2,r2为后序的左右边界
{
if (l1 > r1 || l2 > r2)
{
return;
}
for (int i = l2; i <= r2; i++)
{
if (in[i] == pre[l1])
{
PostOrder(l1 + 1, l1 + i - l2, l2, i - 1);//根据左右子树的个数计算,遍历左子树,左右根
PostOrder(l1 + i - l2 + 1, r1, i + 1, r2);//遍历右子树
printf("%c", pre[l1]);//打印根结点
}
}
}
1.4完整代码
#include <stdio.h>
#include <string.h>
char pre[30];
char in[30];
void PostOrder(int l1, int r1, int l2, int r2)//l1,r1为前序的左右边界,l2,r2为后序的左右边界
{
if (l1 > r1 || l2 > r2)
{
return;
}
for (int i = l2; i <= r2; i++)
{
if (in[i] == pre[l1])
{
PostOrder(l1 + 1, l1 + i - l2, l2, i - 1);//根据左右子树的个数计算,遍历左子树,左右根
PostOrder(l1 + i - l2 + 1, r1, i + 1, r2);//遍历右子树
printf("%c", pre[l1]);//打印根结点
}
}
}
int main()
{
scanf("%s", in);
scanf("%s", pre);
int n = strlen(in) - 1;
PostOrder(0, n, 0, n);
return 0;
}
2.P1305 新二叉树
2.1题目
2.2思路
构造树结构,再根据前序遍历树来打印
2.3完整代码
#include <stdio.h>
struct BiTNode
{
char data;
char lchild;
char rchild;
}tree[300];
void ProOrder(char ch)
{
if (ch != '*')
{
printf("%c", ch);
ProOrder(tree[ch].lchild);
ProOrder(tree[ch].rchild);
}
}
int main()
{
int n;
scanf("%d", &n);
char ch;
for (int i = 0; i < n; i++)//也可以只记录根结点,之后只记录左右,只需要根据根结点遍历左右结点
{
char a, b, c;
scanf(" %c%c%c", &a, &b, &c);
if (i == 0)
{
ch = a;//存根结点字母
}
tree[a].data = a;
tree[a].lchild = b;
tree[a].rchild = c;
}
ProOrder(ch);
return 0;
}
3.P1030 [NOIP2001 普及组] 求先序排列
3.1题目
题目描述
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,且二叉树的节点个数
≤
8
≤8)。
输入格式
共两行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。
输出格式
共一行一个字符串,表示一棵二叉树的先序。
输入输出样例
输入 #1复制
BADC
BDCA
输出 #1复制
ABCD
说明/提示
【题目来源】
NOIP 2001 普及组第三题
3.2思路
与第一道,思路类似,根据中序遍历和后序遍历,打印根结点,来递归划分左子树和右子树,以此来模拟前序遍历
3.3完整代码
#include <stdio.h>//跟第一题同样的方法,
#include <string.h>
char post[30];
char in[30];
void PostOrder(int l1, int r1, int l2, int r2)//l1,r1为后序序列边界,l2,r2为中序序列边界
{
if (l1 > r1 || l2 > r2)
{
return;
}
for (int i = l2; i <= r2; i++)
{
if (in[i] == post[r1])
{
printf("%c", post[r1]);
PostOrder(l1, l1 + i - l2 - 1, l2, i - 1);
PostOrder(l1 + i - l2, r1 - 1, i + 1, r2);
}
}
}
int main()
{
scanf("%s", in);
scanf("%s", post);
int n = strlen(in) - 1;
PostOrder(0, n, 0, n);
return 0;
}
4.P4913 【深基16.例3】二叉树深度
4.1 题目
4.2 思路
先根据输入的来构造树,然后再用后序遍历的变种,来探寻树的深度。
4.3核心代码
遍历左右子树,一直递归到叶子结点,直到为空结点,输出0,再迭代回去,返回左右子树的深度的上一层+1
int depth(int t)
{
if (t == 0)
{
return 0;
}
else
{
int l = depth(tree[t].lchild);
int r = depth(tree[t].rchild);
return l > r ? l + 1 : r + 1;
}
}
4.4完整代码
#include <stdio.h>
struct BitNode
{
int lchild;
int rchild;
}tree[1000010];
int depth(int t)
{
if (t == 0)
{
return 0;
}
else
{
int l = depth(tree[t].lchild);
int r = depth(tree[t].rchild);
return l > r ? l + 1 : r + 1;
}
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d %d", &tree[i].lchild, &tree[i].rchild);
}
int m = depth(1);
printf("%d\n", m);
return 0;
}
5.P1229 遍历问题
5.1 题目
5.2思路
由前序遍历和后序遍历不能确定树的结构,树的只有单个孩子的结点n会影响中序遍历的顺序,单个孩子在左还是在右,有两种情况,所以中序遍历的总数为 2的n次方
5.3完整代码
#include <stdio.h>//找单个孩子的结点数sum,因为孩子可以在左,也可以在右,总个数为2的sum次方,规律题
#include <string.h>
int main()
{
char arr1[300];
char arr2[300];
scanf("%s", arr1);
scanf("%s", arr2);
int sum = 0;
int n = strlen(arr1);
for (int i = 0; i < n; i++)
{
for (int j = 1; j < n; j++)//注意j从1开始防止越界了
{
if (arr1[i] == arr2[j] && arr1[i + 1] == arr2[j - 1])//规律 AB与BA 则B为单个孩子,前序为左孩子,后序中为右孩子,则为单个。
{
sum++;
}
}
}
int ans = 1;
for (int i = 0; i < sum; i++)
{
ans *= 2;
}
printf("%d\n", ans);
return 0;
}
6.P3367 【模板】并查集
6.1题目
6.2思路
利用数组来模拟树结构实现并查集,当z等于1时执行将集合合并的操作,当z等于2时执行查根的操作,看是否属于集合
6.3核心代码
初始化并查集
int n, m;
int arr[20010] = { 0 };
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)//每个独立的集合,本身就只有一个,所以存入-1;
{
arr[i] = -1;
}
实现查找操作Find,并且需要路径压缩来降低时间复杂度
int Find(int s[], int x)
{
int root = x;
while (s[root] >= 0)
{
root = s[root];
}
while (x != root)
{
int t = s[x];
s[x] = root;
x = t;
}
return root;
}
实现合并操作Union,需要先分别找到根结点,将小树挂到大树上,并且加上小树的结点数。
void Union(int s[], int root1, int root2)
{
if (root1 == root2)
{
return;
}
if (s[root1] < s[root2])
{
s[root1] += s[root2];
s[root2] = root1;
}
else
{
s[root2] += s[root1];
s[root1] = root2;
}
}
6.4完整代码
#include <stdio.h>
int Find(int s[], int x)
{
int root = x;
while (s[root] >= 0)
{
root = s[root];
}
while (x != root)
{
int t = s[x];
s[x] = root;
x = t;
}
return root;
}
void Union(int s[], int root1, int root2)
{
if (root1 == root2)
{
return;
}
if (s[root1] < s[root2])
{
s[root1] += s[root2];
s[root2] = root1;
}
else
{
s[root2] += s[root1];
s[root1] = root2;
}
}
int main()
{
int n, m;
int arr[20010] = { 0 };
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)//每个独立的集合,本身就只有一个,所以存入-1;
{
arr[i] = -1;
}
while (m--)
{
int z, x, y;
scanf("%d %d %d", &z, &x, &y);
int a = Find(arr, x);
int b = Find(arr, y);
if (z == 2)
{
if (a == b)
{
printf("Y\n");
}
else
{
printf("N\n");
}
}
if (z == 1)
{
Union(arr, a, b);
}
}
return 0;
}
7.P1551 亲戚
7.1题目
7.2思路
与上道题目的思路类似,也是一道典型的并查集,实现查找和合并两个操作就可以
7.3完整代码
#include <stdio.h>
int s[5100] = { 0 };
int Find(int x)
{
int root = x;
while (s[root] >= 0)
{
root = s[root];
}
while (x != root)
{
int t = s[x];
s[x] = root;
x = t;
}
return root;
}
void Union(int root1, int root2)
{
if (root1 == root2)
{
return;
}
s[root1]+=s[root2];
s[root2] = root1;
}
int main()
{
int n, m, p;
scanf("%d %d %d", &n, &m, &p);
for (int i = 1; i <= n; i++)
{
s[i] = -1;
}
while (m--)
{
int x, y;
scanf("%d %d", &x, &y);
int a, b;
a = Find(x);
b = Find(y);
Union(a, b);
}
while (p--)
{
int x, y;
scanf("%d %d", &x, &y);
int a, b;
a = Find(x);
b = Find(y);
if (a == b)
{
printf("Yes\n");
}
else
{
printf("No\n");
}
}
return 0;
}
8.P2078 朋友
8.1题目
8.2思路
利用两个数组来记录男生和女生的关系并查集,将女的输入的数变为正数(负数 * -1),最后查找两个数组的根结点1所记录的总结点数,取两者之间的较小值(一夫一妻)。
8.3核心代码
必须确保1为根结点,挂到1上,这样才能确定1所含有的总结点数。
void Union(int s[], int root1, int root2)
{
if (root1 == root2)
{
return;
}
if (root1 == 1 && root2 != 1)//确保1为根结点,才能计算
{
s[root1] += s[root2];
s[root2] = root1;
return;
}
if (root2 == 1 && root1 != 1)
{
s[root2] += s[root1];
s[root1] = root2;
return;
}
s[root1] += s[root2];
s[root2] = root1;
}
8.4完整代码
#include <stdio.h>
int min(int a, int b)
{
return a < b ? a : b;
}
int Find(int s[], int x)
{
int root = x;
while (s[root] >= 0)
{
root = s[root];
}
while (x != root)
{
int t = s[x];
s[x] = root;
x = t;
}
return root;
}
void Union(int s[], int root1, int root2)
{
if (root1 == root2)
{
return;
}
if (root1 == 1 && root2 != 1)//确保1为根结点,才能计算
{
s[root1] += s[root2];
s[root2] = root1;
return;
}
if (root2 == 1 && root1 != 1)
{
s[root2] += s[root1];
s[root1] = root2;
return;
}
s[root1] += s[root2];
s[root2] = root1;
}
int main()
{
int s1[11000] = { 0 };
int s2[11000] = { 0 };
int n, m, p, q;
scanf("%d %d %d %d", &n, &m, &p, &q);
for (int i = 1; i <= n; i++)
{
s1[i] = -1;
}
for (int i = 1; i <= m; i++)
{
s2[i] = -1;
}
while (p--)
{
int x, y;
scanf("%d %d", &x, &y);
int a, b;
a = Find(s1, x);
b = Find(s1, y);
Union(s1, a, b);
}
while (q--)
{
int x, y;
scanf("%d %d", &x, &y);
x = -x;
y = -y;
int a, b;
a = Find(s2, x);
b = Find(s2, y);
Union(s2, a, b);
}
int ans = min(-s1[1], -s2[1]);
printf("%d\n", ans);
return 0;
}
9.P1111 修复公路
9.1题目
9.2思路
将按照时间顺序将这些操作数排序,按照时间来依次修路,根结点所记录的总根结点数为 -n 时(数组下标记录的总根结点数为负数),就说明全部通车 ,此时的时间也是最早的时间。如果不等于-n,则没有通车,打印-1。特别注意,排序那里必须用快速排序,不然时间复杂度太高,题目过不去
9.3完整代码
#include <stdio.h>
struct gonglu
{
int a;
int b;
int t;
}arr[100010];
int s[11000] = { 0 };
int Find(int x)
{
int root = x;
while (s[root] >= 0)
{
root = s[root];
}
while (x != root)
{
int t = s[x];
s[x] = root;
x = t;
}
return root;
}
void Union(int root1, int root2)
{
if (root1 == root2)
{
return;
}
if (s[root1] <= s[root2])
{
s[root1] += s[root2];
s[root2] = root1;
}
else
{
s[root2] += s[root1];
s[root1] = root2;
}
}
void quick(int l, int r)
{
if (l > r)
{
return;
}
int i, j;
int temp;
temp = arr[l].t;
struct gonglu t = arr[l];
i = l;
j = r;
while (i != j)
{
while (arr[j].t >= temp && i < j)
{
j--;
}
while (arr[i].t <= temp && i < j)
{
i++;
}
if (i < j)
{
struct gonglu tem = arr[i];
arr[i] = arr[j];
arr[j] = tem;
}
}
arr[l] = arr[i];
arr[i] = t;
quick(l, i - 1);
quick(i + 1, r);
return;
}
int main()
{
int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d %d %d", &arr[i].a, &arr[i].b, &arr[i].t);
}
quick(1, m);//必须得快排,不然时间复杂度太高了
for (int i = 1; i <= n; i++)
{
s[i] = -1;
}
for (int i = 1; i <= m; i++)
{
int x = Find(arr[i].a);
int y = Find(arr[i].b);
Union(x, y);
if (s[Find(1)] == -n)
{
printf("%d\n", arr[i].t);
return 0;
}
}
printf("-1\n");
return 0;
}