问题描述
众所周知,每两周的周三是字节跳动的活动日。作为活动组织者的小A,在这次活动日上布置了一棵 Bytedance Tree。 Bytedance Tree 由 n 个结点构成,每个结点的编号分别为 1,2,3......n,有 n - 1 条边将它们连接起来,根结点为 1。而且为了观赏性,小A 给 M 个结点挂上了 K 种礼物(0 ≤ K ≤ M ≤ N,且保证一个结点只有一个礼物)。 现在小A希望将 Bytedance Tree 划分为 K 个 Special 连通分块,送给参加活动的同学们,请问热心肠的你能帮帮他,告诉小A一共有多少种划分方式吗? 一个 Special 连通分块应该具有以下特性:
- Special 连通分块里只有一种礼物(该种类礼物的数量不限)。
- Special 连通分块可以包含任意数量的未挂上礼物的结点。 由于方案数可能过大,对结果取模 998244353。
问题理解
这段代码主要解决基于树结构的剪边方案计数问题,给定节点数、可剪边次数限制及描述树形态与节点礼物信息的输入。先从输入的树数据里提取礼物和边信息,初始化记录剪边情况的数组。核心在于dfs函数,通过深度优先搜索,以递归方式遍历不同剪边可能,在搜索中依据可剪边次数限制判断边界情况,当达到最大剪边次数时,调用is_valid_block函数检查形成的连通分块是否只含一种礼物来确定方案是否有效,符合就计数。is_valid_block函数则通过构建邻接表分析节点礼物情况判断连通分块有效性。最终solution函数汇总,返回满足条件的剪边方案数量取模后的结果。
代码解析
def solution(nodes, decorations, tree):
MOD = 998244353
# 提取礼物信息和边信息
gifts = tree[0]
edges = tree[1:]
# 确保 gifts 数组的长度与节点数一致
if len(gifts) < nodes:
gifts.extend([0] * (nodes - len(gifts)))
# 记录剪边的数组
cut_edges = [False] * (nodes- 1)
# 判断连通分块是否只包含一种礼物的函数
def is_valid_block(cut_edges):
# 初始化邻接表
adj = [[] for _ in range(nodes + 1)]
for i in range(nodes-1):
if cut_edges[i] == False:
u, v = edges[i]
adj_u_set = set(adj[u])
for w in adj[v]:
if w not in adj_u_set:
adj[u].append(w)
adj_u_set.add(w)
adj[u].append(v)
adj_v_set = set(adj[v])
for w in adj[u]:
if w not in adj_v_set:
adj[v].append(w)
adj_v_set.add(w)
adj[v].append(u)
for i in range(nodes):
# print(gifts[i],adj[i+1])
if gifts[i]!=0:
for node in adj[i+1]:
#print(gifts[node-1],gifts[i])
if gifts[node-1]!=gifts[i] and gifts[node-1]!=0:
return False
return True
count=0
# 深度优先搜索函数
def dfs(i, cut_count):
nonlocal count # 使用 nonlocal 关键字访问外部作用域的 count 变量
if cut_count > decorations - 1 or i>=nodes-1:
return 0
#如果剪掉这条边
if cut_count == decorations-1:
#print(i,cut_count)
if is_valid_block(cut_edges):
count+=1
#print (cut_edges,is_valid_block(cut_edges))
return 0
cut_edges[i]=True
dfs(i+1,cut_count+1)
cut_edges[i]=False
dfs(i+1,cut_count)
return 0
# 从根节点开始 DFS
dfs(0,0)
return count % MOD
if __name__ == "__main__":
# You can add more test cases here
testTree1 = [[1,0,0,0,0,2,3],[1,7],[3,7],[2,1],[3,5],[5,6],[6,4]]
testTree2 = [[1,0,1,0,2],[1,2],[1,5],[2,4],[3,5]]
tree = [[1, 2, 0, 1, 0, 2], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
print(solution(7, 3, testTree1) )
print(solution(6, 2, tree) )
知识总结
- 整体功能与算法思路 代码旨在解决特定树结构下满足一定条件的剪边方案计数问题。通过深度优先搜索(DFS)结合判断连通分块礼物种类的规则,遍历所有可能的剪边情况,统计符合条件(即剪边后各连通分块只含一种礼物)的方案数量,并对结果进行取模操作,运用了递归和图(树)相关处理的思路来实现目标。
- 代码结构关键部分
数据初始化与提取:从输入的
tree结构中提取礼物信息存为gifts,边信息存为edges,并补充gifts长度确保与节点数匹配,同时初始化记录边是否被剪的cut_edges数组,为后续处理做准备。is_valid_block函数:核心是构建邻接表adj来体现树在当前剪边状态下的连通关系,通过遍历节点及邻接节点的礼物情况,判断连通分块是否满足只含一种礼物的条件,体现了图结构中节点关系梳理及条件判断逻辑。dfs函数:利用递归实现深度优先搜索,通过参数传递当前边索引和已剪边数量,依据边界条件决定搜索终止或继续,在合适时机调用is_valid_block判断方案有效性并计数,展现递归探索不同状态的算法应用。 - 涉及的知识点
Python语法特性:像
nonlocal关键字用于在嵌套函数中访问外部作用域变量;列表推导、列表操作(如append等)用于构建和处理邻接表等数据结构;取模运算的应用体现了对数值处理的操作。 数据结构运用:使用列表来存储树的节点礼物信息、边信息以及记录剪边状态等,还通过邻接表(二维列表形式)表示树的连通关系,辅助判断连通分块情况,展示了不同数据结构在实际问题解决中的配合使用。 算法思想体现:深度优先搜索递归地遍历剪边的所有可能情况,结合判断连通分块有效性的规则,整体是一种基于搜索和条件判断的组合计数算法思路,体现了如何将实际问题转化为可通过特定算法解决的编程实现过程。
学习计划
学习目标 :在短期内掌握解决Bytedance Tree划分连通分块问题的方法。 资料准备 :备好《算法导论》等书籍,注册慕课网等在线平台,利用好LeetCode等编程实践网站。 学习进度 : 第1周:复习树结构知识,掌握存储与遍历方式,练习相关简单题。 第2周:巩固动态规划与组合计数基础,做对应练习题,熟悉应用思路。 第3周:深入剖析问题,结合所学构思算法,尝试代码实现。 第4周:优化代码,多做类似题目强化理解,确保能独立解决此类问题。
学习建议
先巩固树结构基础知识,熟悉其概念、遍历方式等,多练习简单题目。再掌握动态规划与组合计数要点,理解原理与应用思路。认真剖析 Bytedance Tree 问题,梳理关键要素,结合所学知识尝试推导、写代码,多调试优化,逐步攻克难题。