假设树具有 n 顶点和 n-1 边缘。所有节点都已连接。我想将树分成几部分,每个部分必须包含至少 2 个顶点。
我可以用多少种方法做到这一点?
示例:的
1
/ \
2 3
/ \ \
4 5 6
对于上面给出的树,答案是 3
1. [1,2,3,4,5,6]
2. [1,3,6] and [2,4,5]
3. [3,6] and [1,2,4,5]
1 1 1
/ \ \ /
1) 2 3 2) 2 3 3) 2 3
/ \ \ / \ \ / \ \
4 5 6 4 5 6 4 5 6
答案 0 :(得分:2)
这是一个线性时间DP。设P(u)
为以u
为根的子树的有效分区数。让A(u)
为"几乎有效"以u
为根的子树的分区,即除了u
之外的每个节点都属于具有至少两个顶点的部分。我们有复发
P(u) = product_{v in Children(u)} (2 P(v) + A(v)) - A(u)
A(u) = product_{v in Children(u)} P(v),
可以在线性时间内进行评估。
重复背后的直觉是对u
进行案例分析,u
是所考虑的子树的根。
u
与其子女无关。分区几乎是有效的;所有以u
子项为根的子树都有有效分区。
1
/ \
2 3
/ \ \
4 5 6
与其一个或多个孩子在一起。同一部分的孩子有效或几乎有效;不同部分的孩子是有效的。有效的孩子有两种选择(与父母一起加入或不加入);几乎有效的孩子都有一个(加入他们的父母)。
例如,在树上
A(4) = 1 (empty product is 1)
P(4) = 1 - A(4) = 0
A(5) = 1
P(5) = 1 - A(5) = 0
A(2) = P(4) P(5) = 0
P(2) = (2 P(4) + A(4)) (2 P(5) + A(5)) - A(2) = 1
A(6) = 1
P(6) = 1 - A(6) = 0
A(3) = P(6) = 0
P(3) = (2 P(6) + A(6)) - A(3) = 1
A(1) = P(2) P(3) = 1
P(1) = (2 P(2) + A(2)) (2 P(3) + A(3)) - A(1) = 3.
我们有
1
/ \
2 3
\
6
在树上
A(2) = 1
P(2) = 1 - A(2) = 0
A(6) = 1
P(6) = 1 - A(6) = 0
A(3) = P(6) = 0
P(3) = (2 P(6) + A(6)) - A(3) = 1
A(1) = P(2) P(3) = 0
P(1) = (2 P(2) + A(2)) (2 P(3) + A(3)) - A(1) = 2.
我们有
double calcTipsPerHour = totalTips / resultTotalHours;
答案 1 :(得分:1)
我想出了一个解决方案。
我的脚本未经过优化,可能需要很长时间才能运行。此外,它假设树是二进制的,就像你在问题中给出的例子一样。
def count_partitions(tree):
if len(tree.nodes()) <= 1:
return 0
elif len(tree.nodes()) == 2:
return 1
else:
count = 1
for edge in tree.edges():
upper, lower = tree.cut(edge)
count += count_partitions(upper) * count_partitions(lower)
return count
那么,它是如何运作的。
事实上,如果树有零个或一个节点,则不能在每个至少两个节点的树中进行分区。如果一棵树有两个节点,那么只有一个这样的分区。
如果树具有大于2的任意数量的节点,则至少存在一个这样的分区(count = 1
)。所以一个接一个地(for edge in tree.edges
),我从树中移除每个边(tree.cut(edge)
)。删除边缘的这种操作导致两棵树。然后,分区数等于这两个子树(count_partitions(upper) * count_partitions(lower)
)的分区数的乘积。
你可以在你的例子上测试它;它将按预期返回3
。
关于复杂性,它并不是很好。我不知道是否还有其他方法可以提高效率,但是这个方法在我看来就像 O(卡片(边缘)!):在给定状态下{ {1}}边,对于每条边,执行n
个其他边上的循环。
以下是对随机创建的小树执行时间的评估:
实施细节
我写了一个n-1
类(和一个Tree
)类,它主要有三种方法:Node
,nodes
和edges
。我很快就编写了这个脚本,所以我只想尝试一些有效的东西,无论效率如何。必须有更好的方法来实现这一点。
cut
方法是最重要的(不过它真的很复杂)。它沿着边缘分割两部分树,然后返回两棵新树。
Tree.cut
<强>更新强>
正如评论中所讨论的,我试图记住这个功能。
我选择记住树的节点,因为它在我的实现中没问题。结果当然比以前更好;这是比较执行时间:
在main函数的过程中多次创建相同的子树,因此memoization非常有用。
这是memoization的代码(非常具体到这个实现):
def cut(self, edge):
upper_tree = Tree(self)
lower_tree = Tree()
for node in upper_tree.nodes():
if node.value == edge[0].value:
if node.left and node.left.value == edge[1].value:
lower_tree.root = node.left
node.left = None
elif node.right and node.right.value == edge[1].value:
lower_tree.root = node.right
node.right = None
return (upper_tree, lower_tree)