noip2017最后一次普及组总结

作者分享了自己参加初赛和复赛的经历,初赛状态良好,复赛则因首次参赛而感到紧张。详细介绍了四道题目的解题思路及遇到的问题,如快排忘记使用、未注意到题目的细节等,并对比赛进行了总结。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(注:本文在我心情不是那么好的时候写的,详见到复赛第二题)

初赛:

 状态还好,考了66分,考完之后就去放松了。

复赛:

 由于是第一次进复赛,导致有点紧张。

第一题:
 简单的模拟,就是“a*0.2+b*0.3+c*0.5”而已

第二题:

 思路是用快排将所有编码按长度排,在从最短的开始找。但因为紧张,快排忘打了,不过数据范围应该允许,就用了个选排混了过去。不过!!!没看到要选最小的!!!因此我只是排了编码长度,应该只能拿20分了。。。下次要仔细点了啊!!!ヾ(。`Д´。)现在回想起来,硬要用字符串来做的话就用快排从小到大排,然后再转成字符串吧。。。


 补充:源程序洛谷爆蛋了。。。只得到了图书编码是从大到小排列的20分


第三题:

 有人说记忆化搜索,有人说DP,有人说用广搜也能100(民间数据),反正我就是用深搜的。不过不知道为什么连测试数据都不能全部遍历,一到终点就退出可能不是最优解。便想到了优先级,优先去同种颜色的,再去不同颜色的,实在不行就去没颜色的吧╮(╯▽╰)╭

 补充:优先级深搜20分


第四题:

 看不懂,愿输出-1能有一点分吧!(0分)(注:听人说是二分贪心。。。)


总结:

 不要过于紧张,要审好题,要多想容易错的测试数据,留至少10分钟检查文件名(个人是还剩1小时时就弄好了,然后再去优化第三题,最后再交好)

<think>嗯,用户问的是关于NOIP2018普及组对称二叉树的解题思路和实现方法。首先,我需要回忆一下题目要求。题目是要求找出二叉树中节点数最多的对称子树,对吧?根据引用中的描述,对称二叉树指的是结构对称且节点值对称的子树。那首先我得明确什么是对称二叉树的结构和值的对称性。 根据引用[4],对称二叉树需要满足左右子树镜像对称,并且节点的值也要相同。结构对称可能意味着左子树的左孩子和右子树的右孩子对称,左子树的右孩子和右子树的左孩子对称,类似镜像。那如何遍历每个节点,检查以该节点为根的子树是否对称呢? 用户提供的引用中有提到DFS的方法,比如引用[2]和引用[5]提到DFS遍历子树,检查对称性。可能的步骤是:先预处理每个子树的节点数,这样在后续检查时可以快速剪枝,比如如果当前子树节点数比已知的最大值还小,就可以跳过。然后对每个节点,检查其左右子树是否对称,同时记录最大的节点数。 那具体如何实现对称检查呢?递归函数可能是这样的:输入两个节点,判断它们是否对称。即它们的值相等,并且左子节点与右子节点的对称,右子节点与左子节点的对称。这一步需要递归处理。例如,函数isSymmetric(left, right)返回是否对称。 另外,预处理每个子树的节点数可以通过后序遍历来实现,如引用[5]中的dfs1函数,先递归计算左右子树的节点数,再加起来加上自己。这样每个节点的cnt[u]就是该子树的节点总数。这一步可以在O(n)时间内完成。 接下来,主函数需要遍历每个节点,对于每个节点,检查以它为根的子树是否对称。如果对称,就更新最大节点数。这里如何优化呢?比如,如果当前子树的节点数已经小于当前最大值,那么可以跳过检查,因为即使对称,也无法更新最大值。这样可以减少不必要的检查,提升效率。 需要注意的是,对称二叉树的定义中,如果子树本身是单节点(即没有子节点),也算对称,所以每个叶子节点本身就是一个对称子树,节点数为1。因此,初始的最大节点数可以设为1,然后在遍历过程中不断更新。 现在,如何具体实现这个检查对称性的函数呢?递归的终止条件是什么?当两个节点都为空时,对称;当一个为空另一个不为空时,不对称。否则,比较它们的值,然后递归比较左节点的左子树和右节点的右子树,以及左节点的右子树和右节点的左子树是否对称。 接下来,主算法的大致流程可能是: 1. 预处理每个子树的节点数,存储到cnt数组中。 2. 遍历每个节点u,如果cnt[u] <= current_max,则跳过,否则进行对称性检查。 3. 对节点u的左右子树进行对称检查,如果通过,则更新current_max为cnt[u]。 4. 遍历结束后,输出current_max。 这里可能的优化点在于剪枝,即当某个子树的节点数已经不大于当前最大值时,就不需要检查它是否对称了,因为即使对称也无法更新最大值。这样可以节省时间。 关于时间复杂度,预处理每个节点的子树大小是O(n),遍历每个节点是O(n),而每次对称检查的时间复杂度最坏情况下是O(n),所以总的时间复杂度是O(n^2),对于n较大的情况可能不够高效。但根据题目中的普及组难度,可能这样的解法是可以接受的。有没有更优的方法呢? 比如,在检查对称性时,如果子树结构本身不对称,或者节点值不匹配,可以提前终止递归。此外,引用[5]中提到的剪枝策略,即如果当前子树的节点数已经无法超过当前最大值,就跳过,这样可以减少很多不必要的检查。 在实现时,需要注意空节点的处理。例如,左右子节点可能为0,这时候需要判断是否对称。 现在,针对代码实现,可能需要先构建二叉树的结构,比如每个节点的左右子节点。输入格式可能按照数组方式给出,每个节点的左右子节点索引。例如,每个节点i的左子节点是le[i],右子节点是rt[i]。同时,需要存储每个节点的值val[i]。 预处理函数dfs1(u)计算cnt[u],递归计算左子树和右子树的节点数之和加1。然后,对于每个节点u,如果cnt[u] > max_count,则进行对称检查。对称检查函数需要从根节点u的左右子树开始,即检查le[u]和rt[u]是否对称。 例如,函数check(a, b)判断以a和b为根的两棵树是否对称。如果a和b都为空,返回true;一个为空另一个不为空,返回false。否则,检查val[a]是否等于val[b],然后递归check(le[a], rt[b])和check(rt[a], le[b])。只有当两者都满足时,才返回true。 所以,当检查以u为根的子树是否对称时,需要检查u的左子树和右子树是否对称。如果u没有左子树或右子树,那是否可能对称?例如,当u只有左子树而没有右子树时,只有当左子树的结构和右子树(空)对称,也就是左子树也为空时,但此时u的子树结构就是u本身和左子树,但根据对称的定义,可能只有根节点的情况才对称。例如,如果u的左右子树都为空,则对称;如果其中一个非空,另一个为空,则不对称。 这时候,对于以u为根的子树是否对称的判断,需要分为两种情况:如果u没有左右子节点,则对称;如果u有一个子节点,则不对称;如果u有两个子节点,则需要它们的左右子树对称。 或者,可能更准确的是,对称二叉树的定义是整个子树的结构和值对称。因此,对于根节点u来说,其左右子树必须镜像对称。所以,如果u的左子树和右子树都为空,则对称;如果只有其中一个为空,则不对称;否则需要递归判断。 因此,判断以u为根的子树是否对称的条件是:其左右子树是否对称。所以,在代码中,检查对称的函数应该传入u的左子节点和右子节点,而不是u本身。例如: isSymmetricTree(u) { return check(le[u], rt[u]); } 而check函数递归判断两个节点是否对称。 现在,整体算法的大致步骤是: 1. 输入数据,构建树的结构,包括每个节点的左右子节点和值。 2. 预处理每个节点的子树大小cnt[u]。 3. 初始化max_count为1(因为单节点也算对称)。 4. 遍历每个节点u: a. 如果cnt[u] <= max_count,跳过。 b. 否则,检查以u为根的子树是否对称。 c. 如果对称,更新max_count为cnt[u]。 5. 输出max_count。 这样,时间复杂度取决于预处理和每个节点的检查。预处理是O(n),遍历是O(n),每次检查是O(k)(k是该子树的节点数)。所以总的时间复杂度是O(n^2)。对于n=1e5的情况,这样的复杂度可能过高,但普及组的题目可能数据规模较小,比如n<=1e3或者更小,所以这样的解法可能可行。 引用[5]中的代码展示了预处理cnt数组的方法,以及如何剪枝。此外,在遍历时,如果当前节点的cnt[u]已经不大于max_count,则无需检查,这样可以节省时间。 现在,关于具体实现中的细节问题,比如节点的编号是否从1开始,如何处理输入等。例如,题目中的输入格式可能是每个节点给出左右子节点的编号,0表示空。比如,对于节点i,le[i]和rt[i]分别表示左子和右子的编号,0表示不存在。 代码实现的话,可以用Python中的字典或数组来存储每个节点的左右子节点和值。例如,用数组存储,索引为节点编号。 在Python中,可能的实现步骤: 读取n,然后读取n行的数据,每行包括val[i], le[i], rt[i]。注意,这里的le和rt可能是指向其他节点的编号,例如,如果节点i的左子是j,则le[i] = j。如果为0,则表示没有。 预处理cnt数组:通过后序遍历每个节点,计算其子树大小。可以用递归或者迭代的方式。 然后,对于每个节点u,检查是否可能成为更大的对称子树。即,如果cnt[u] > current_max,则调用isSymmetric检查。如果是,则更新current_max。 而isSymmetric函数需要比较两个子树是否对称。这里,传入的参数应该是两个节点的编号,例如a和b。函数判断以a为根和以b为根的树是否镜像对称。 所以,函数的大致结构: def check(a, b): if a == 0 and b == 0: return True if a == 0 or b == 0: return False if val[a] != val[b]: return False return check(le[a], rt[b]) and check(rt[a], le[b]) 这样,当需要检查以u为根的子树是否对称时,只需要检查check(le[u], rt[u])。但是,如果u的左右子树都是空,则此时check返回True,所以整个子树是对称的。但是如果u的左子树或者右子树有一个存在,另一个不存在,则返回False。例如,当u只有一个子节点时,其子树的结构是不对称的。因此,当u的左右子树都为空时,对称;否则需要检查左右子树是否对称。 但根据题目的定义,对称二叉树的根节点的左右子树必须镜像对称。所以,对于根节点u来说,其左子树和右子树必须镜像对称,否则整个子树就不是对称的。因此,在判断以u为根的子树是否对称时,必须满足: 如果u没有左右子节点(即左右都是0),则对称,节点数为1。 如果u有左或右子节点中的一个,另一个为空,则不对称。 如果u有左右子节点,则需要这两个子节点的子树是镜像对称的。 所以,判断以u为根的子树是否对称的条件是: 当且仅当u的左右子树都为空,或者存在左右子树,并且它们的子树是镜像对称的。 因此,在代码中,判断以u为根的子树是否对称的函数应该是: def is_symmetric_root(u): if le[u] == 0 and rt[u] == 0: return True return check(le[u], rt[u]) 而check函数如前所述,递归判断两个子树是否对称。 现在,针对特殊情况,比如u的左右子节点存在,但val不同,那么显然不对称。或者结构上不对称,比如左子树的左子树存在而右子树的右子树不存在。 这样,整个算法的思路就比较清晰了。现在,如何将这些步骤转化为具体的代码? 例如,Python代码的大致结构: n = int(input()) nodes = [[] for _ in range(n+1)] # 假设节点编号从1到n for i in range(1, n+1): val, le, rt = map(int, input().split()) nodes[i] = (val, le, rt) cnt = [0]*(n+1) def dfs(u): if u == 0: return 0 left_cnt = dfs(nodes[u][1]) right_cnt = dfs(nodes[u][2]) cnt[u] = left_cnt + right_cnt + 1 return cnt[u] dfs(1) # 假设根节点是1?或者题目中根节点是哪个? max_size = 1 def check(a, b): if a ==0 and b ==0: return True if a ==0 or b ==0: return False if nodes[a][0] != nodes[b][0]: return False return check(nodes[a][1], nodes[b][2]) and check(nodes[a][2], nodes[b][1]) for u in range(1, n+1): if cnt[u] <= max_size: continue if nodes[u][1] ==0 and nodes[u][2] ==0: # 叶子节点,对称,节点数为1,但此时cnt[u]=1,max_size可能已经是更大的值,所以不会进入这个循环? # 所以这种情况可能已经被初始max_size=1覆盖 pass else: if check(nodes[u][1], nodes[u][2]): max_size = cnt[u] print(max_size) 但这里可能存在错误,比如根节点的处理。例如,当根节点是1,但可能树的结构中存在多个节点。例如,当u是根节点时,检查它的左右子树是否对称。如果对称,则整个根节点的子树是对称的,节点数是cnt[u]。 例如,假设根节点是1,其左右子树对称,那么整个树就是对称的,节点数是cnt[1]。这可能成为最大的情况。 但是,上述代码中的循环遍历每个节点u,检查它的左右子树是否对称。此时,当u的左右子树对称时,以u为根的子树是对称的,所以节点数是cnt[u]。 但是,在代码中,当检查u是否为对称子树的根时,应该判断其左右子树是否对称。例如,当u的左右子树都为空时,即le[u]和rt[u]都是0,那么此时u本身是对称的,cnt[u]=1。如果max_size初始化为1,那么此时不会更新,因为cnt[u]等于max_size。 而当u的左右子树存在且对称时,cnt[u]可能较大,从而更新max_size。 但有一个问题:当u的左右子树对称时,整个子树以u为根的结构是否对称?是的。因为对称二叉树的定义是,根节点的左右子树必须镜像对称,并且每个子树也满足这样的条件。因此,通过check函数递归检查左右子树是否镜像对称,可以保证整个子树是对称的。 现在,测试这种情况是否正确。例如,假设u的左右子节点a和b,它们的结构对称且值相同,那么整个u的子树是对称的,节点数是cnt[u] = cnt[a] + cnt[b] + 1。 那代码中的循环部分,遍历每个节点u,计算其cnt[u],然后如果cnt[u] >当前max_size,则检查其左右子树是否对称。如果是,更新max_size。 这样,这个算法的时间复杂度对于每个节点u来说,检查对称的时间是O(k),k是该子树的节点数。总的时间复杂度是O(n^2),因为每个节点可能被检查多次,但通过剪枝,当cnt[u] <= max_size时,直接跳过。 对于普及组的题目,这样的解法可能在时间上是可行的,因为数据范围可能较小,比如n<=1e3的话,1e6次操作是可行的。 现在,关于代码中的根节点是否正确。例如,在输入中,根节点是否是1?题目中的输入可能给出每个节点的左右子节点,根节点可能由输入确定。例如,题目中的输入格式可能给出每个节点的顺序,比如节点编号是1~n,其中根节点是1。或者可能需要根据输入的结构确定根节点?比如,根节点是没有父节点的节点? 但题目中的输入格式可能如引用[3]所述:“输入格式:第一行一个正整数n,表示给定的树的节点的数目;第二行n个正整数,用一个空格分隔,第i个正整数代表节点i的权值。接下来n行,每行两个整数lc和rc,分别表示第i个节点的左、右子节点编号。如果某个子节点不存在,用-1表示。” 但具体输入格式可能需要根据题目描述来确定。在用户给出的引用中,引用[3]提到输入格式可能包括每个节点的左右子节点,例如lc和rc,其中可能用0或者其他符号表示不存在。例如,在用户提供的引用[5]中的代码示例中,使用le[u]和rt[u]来存储左右子节点,其中0表示空。 因此,在代码中,假设输入中的每个节点的左右子节点用0表示不存在,节点编号从1到n,根节点是1或者其他某个节点?比如,根节点可能没有被任何其他节点引用,所以需要找出哪个节点是根节点。 例如,在输入中,除了根节点外,其他节点都会被某个父节点引用。因此,可以通过遍历所有节点,找出没有父节点的那个节点作为根节点。例如,建立一个数组parent,初始化为0,然后对于每个节点i的左子和右子,将他们的parent设为i。最后,根节点是parent数组中为0的那个节点。 这可能是一个必要的步骤,但用户提供的引用中的代码似乎没有处理这个问题,例如引用[5]中的代码似乎直接以某个节点为根进行遍历,可能假设根节点是固定的。例如,题目中的输入可能规定根节点是1。这可能根据题目具体输入格式而定。 因此,在代码中,可能需要首先确定根节点。例如,用户提供的引用[3]中的输入格式是否明确根节点的编号?例如,可能根节点是1? 假设题目中的输入规定,节点按照1到n的顺序给出,根节点是1。因此,在代码中,预处理时以1作为根节点进行dfs。但实际情况可能不同,所以需要根据题目具体要求处理。例如,在题目给出的输入中,根节点可能不是1,所以必须通过父节点的引用情况确定根节点。 例如,在代码中,可以维护一个数组,记录每个节点是否有父节点。初始化时所有节点为False。然后,遍历每个节点的左子和右子,如果存在(非0),则将其父节点标记为True。最后,根节点是那个未被标记的节点。 例如: n = int(input()) has_parent = [False]*(n+1) nodes = [[] for _ in range(n+1)] for i in range(1, n+1): val, lc, rc = map(int, input().split()) nodes[i] = (val, lc, rc) if lc !=0: has_parent[lc] = True if rc !=0: has_parent[rc] = True root = 0 for i in range(1, n+1): if not has_parent[i]: root = i break 这样,找到根节点后,再进行后续处理。 这样,预处理cnt数组时,应该从root开始dfs。例如,调用dfs(root)。 这可能是一个必要的步骤,否则如果根节点不是1,那么原来的代码中的dfs(1)会导致错误。 因此,在用户提供的引用[5]的代码中,可能假设根节点是1,而实际题目中可能需要根据输入确定根节点。 因此,在解题思路中,需要指出需要首先确定根节点,这可以通过遍历所有节点,找出没有父节点的那个节点。 综上,正确的解题步骤应包括: 1. 输入处理,构建每个节点的值和左右子节点。 2. 确定根节点(没有被任何节点作为子节点的节点)。 3. 预处理每个节点的子树大小cnt[u]。 4. 遍历每个节点,检查其作为根的子树是否对称,并更新最大节点数。 5. 输出最大节点数。 现在,结合这些步骤,可以总结解题思路: 首先,确定根节点。然后,预处理每个子树的大小。接着,遍历每个可能的子树根节点,检查该子树是否对称。如果是对称的,并且其节点数大于当前最大值,则更新最大值。检查对称性通过递归比较左右子树的结构和值是否镜像对称。 在实现时,需要注意递归的终止条件和空节点的处理。同时,剪枝步骤(当当前子树的节点数不大于当前最大值时跳过)能有效提高效率。 可能的错误点包括:根节点未正确确定,导致预处理错误;递归检查对称时未正确处理空节点;未考虑单节点子树的情况等。 此外,时间复杂度分析:假设n是节点数,预处理cnt数组是O(n),遍历每个节点是O(n),每次检查对称在最坏情况下是O(n),总的时间复杂度是O(n²)。对于n=1e5的数据,这显然会超时,但普及组的题目可能数据规模较小,如n<=1e3,这样的解法可以通过。 但有没有更优的算法呢?比如,线性时间的解法?可能较为困难,因为需要检查每个子树是否对称,这可能无法在低于O(n²)的时间内完成。 综上,该题的解题思路大致如上,现在需要将其整理成用户容易理解的中文回答,并按照用户的要求,结构清晰,分步骤说明,并给出代码示例。</think>### 对称二叉树解题思路与实现 #### 问题理解 题目要求找出给定二叉树中节点数最多的对称子树。对称二叉树的定义是: 1. 结构对称:左子树和右子树镜像对称 2. 值对称:对应节点的值相等 3. 子树定义:包含根节点及其所有后代节点[^3] #### 算法设计 ##### 步骤一:预处理子树大小 通过后序遍历计算每个节点的子树大小,存储为 $cnt[u]$: $$cnt[u] = cnt[left] + cnt[right] + 1$$ 这一步的复杂度为 $O(n)$[^5]。 ##### 步骤二:对称性检查 递归判断两棵子树是否镜像对称: 1. 若两节点均为空:对称 2. 若一空一非空:不对称 3. 节点值不等:不对称 4. 递归检查左子树的左子节点与右子树的右子节点,左子树的右子节点与右子树的左子节点[^2] 函数定义: ```python def check(a, b): if a == 0 and b == 0: return True if a == 0 or b == 0: return False if val[a] != val[b]: return False return check(left[a], right[b]) and check(right[a], left[b]) ``` ##### 步骤三:剪枝优化 遍历所有节点时,若当前子树大小 $cnt[u] \leq max\_count$,直接跳过检查[^5]。 #### 完整代码实现 ```python n = int(input()) val = [0] * (n + 1) left = [0] * (n + 1) right = [0] * (n + 1) has_parent = [False] * (n + 1) for i in range(1, n + 1): v, lc, rc = map(int, input().split()) val[i], left[i], right[i] = v, lc, rc if lc != 0: has_parent[lc] = True if rc != 0: has_parent[rc] = True # 找根节点 root = 0 for i in range(1, n + 1): if not has_parent[i]: root = i break cnt = [0] * (n + 1) def dfs(u): if u == 0: return 0 cnt[u] = dfs(left[u]) + dfs(right[u]) + 1 return cnt[u] dfs(root) max_size = 1 def check(a, b): if a == 0 and b == 0: return True if a == 0 or b == 0: return False if val[a] != val[b]: return False return check(left[a], right[b]) and check(right[a], left[b]) for u in range(1, n + 1): if cnt[u] <= max_size: continue if check(left[u], right[u]): max_size = cnt[u] print(max_size) ``` #### 关键点说明 1. **根节点查找**:通过标记父节点确定根节点 2. **子树大小预处理**:后序遍历计算节点数 3. **对称性检查**:递归实现镜像比较 4. **剪枝优化**:跳过不可能更新答案的子树 #### 复杂度分析 - 时间复杂度:$O(n^2)$(最坏情况) - 空间复杂度:$O(n)$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值