Stern–Brocot 树与 Farey 序列 连分数的树 有两种主要方法,可以将所有可能的连分数,合并为有用的树结构。
Stern–Brocot 树 定义 Stern–Brocot 树是一种维护分数的优雅的结构,包含所有不同的正有理数。这个结构由 Moritz Stern 在 1858 年和 Achille Brocot 在 1861 年发现。
解释 Stern–Borcot 树从两个简单的分数开始:
这个 可能看得你有点懵逼。不过我们不讨论这方面的严谨性,你只需要把它当作 就行了。
每次在相邻的两个分数 中间插入一个分数 ,这样就完成了一次迭代,得到下一个序列。于是它就会变成这样
既然称它为 Stern–Brocot 树,那么它总得有一个树的样子。来一张图:
可以把第 层的序列当作是深度为 的 Stern–Brocot 树的中序遍历。
性质 接下来讨论 Stern–Brocot 树的性质。
单调性 在每一层的序列中,真分数是单调递增的。
略证:只需要在 的情况下证明
就行了。这个很容易,直接做一下代数变换即可
另一边同理可证。
最简性 序列中的分数(除了 )都是最简分数。
略证:为证明最简性,我们首先证明对于序列中连续的两个分数 :
显然,只需要在 的条件下证明 的情况成立即可。
后半部分同理。证明了这个,利用扩展欧几里德定理,如果上述方程有解,显然 。这样就证完了。
有了上面的证明,可以证明 。
有了这两个性质,就可以把它当成一棵平衡树来做了。建立和查询就向平衡树一样做就行了。
连分数表示与父子节点 递推 ,意味着连分数表示对树中 的路径进行编码。要查找 ,必须使 向右移动, 向左移动, 向右移动等等,直到 。
连分数节点 的父节点是沿最后使用的方向后退一步获得的分数。
换句话说,当 时,它是 ,而当 时,则是 。
因此, 的子节点是 和 。
索引 为 Stern Brocot 树编制索引。根顶点被分配了一个索引 。然后,对于顶点 ,通过将 的前导位从 更改为 来分配其左子节点的索引,对于右子节点,通过将前导位从 更改为 来分配索引:
在这种索引中,连分数表示规定了有理数的 游程长度编码 (run-length encoding)。
对于 ,其索引为 ,其游程长度编码(考虑按升序排列的位)为 。
另一个例子是 ,其索引为 ,其游程编码实际上为 。
值得注意的是,Stern–Brocot 树实际上是一个堆。也就是说,它是 的二叉树,它也是 和 的堆。
实现 构建实现
C++ Python
void build ( int a = 0 , int b = 1 , int c = 1 , int d = 0 , int level = 1 ) {
int x = a + c , y = b + d ;
// ... output the current fraction x/y
// at the current level in the tree
build ( a , b , x , y , level + 1 );
build ( x , y , c , d , level + 1 );
}
def build ( a = 1 , b = 1 , c = 1 , d = 0 , level = 1 ):
x = a + c
y = b + d
# ... output the current fraction x/y
# at the current level in the tree
build ( a , b , x , y , level + 1 )
build ( x , y , c , d , level + 1 )
查询实现
C++ Python
string find ( int x , int y , int a = 0 , int b = 1 , int c = 1 , int d = 0 ) {
int m = a + c , n = b + d ;
if ( x == m && y == n ) return "" ;
if ( x * n < y * m )
return 'L' + find ( x , y , a , b , m , n );
else
return 'R' + find ( x , y , m , n , c , d );
}
def find ( x , y , a = 0 , b = 1 , c = 1 , d = 0 ):
m = a + c
n = b + d
if x == m and y == n :
return ""
if x * n < y * m :
return "L" + find ( x , y , a , b , m , n )
else :
return "R" + find ( x , y , m , n , c , d )
例题 最佳内点 对于 ,找到有理数 使得 在字典序最小,并且 。
解答 就 Stern–Brocot 树而言,这意味着需要找到 和 的 LCA。由于 Stern–Brocot 树和连分数之间的联系,该 LCA 将大致对应于 和 的连分数表示的最大公共前缀。
因此,如果 和 是无理数,则 LCA 为 。
对于有理数 和 ,其中之一可能是 LCA 本身,这需要对其进行讨论。为了简化有理数 和 的解决方案,可以使用前面问题中导出的 和 的连分数表示。
1
2
3
4
5
6
7
8
9
10
11
12
13 # finds lexicographically smallest (q, p)
# such that p0/q0 < p/q < p1/q1
def middle ( p0 , q0 , p1 , q1 ):
a0 = pm_eps ( fraction ( p0 , q0 ))[ 1 ]
a1 = pm_eps ( fraction ( p1 , q1 ))[ 0 ]
a = []
for i in range ( min ( len ( a0 ), len ( a1 ))):
a . append ( min ( a0 [ i ], a1 [ i ]))
if a0 [ i ] != a1 [ i ]:
break
a [ - 1 ] += 1
p , q = convergents ( a )
return p [ - 1 ], q [ - 1 ]
GCJ 2019, Round 2 - New Elements: Part 2 您得到 个正整数对 。您需要找到一个正整数对 ,这样 就是一个严格递增的序列。
在这类配对中,找到词典中最小的一对。
解答 重新表述语句, 对于所有 都必须为正,其中 , 。
在这些方程中,对于 ,有四个情况:
可以忽略,因为正在查找 。 将提供「IMPOSSIBLE」作为答案。 , 。这样的约束相当于 。 , 。这样的约束相当于 。 让 是第四组中最大的 ,而 则是第三组中最小的 。
现在的问题是,给定 ,找到一个分数 使得 在字典上最小,并且 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 def solve ():
n = int ( input ())
C = [ 0 ] * n
J = [ 0 ] * n
# p0/q0 < y/x < p1/q1
p0 , q0 = 0 , 1
p1 , q1 = 1 , 0
fail = False
for i in range ( n ):
C [ i ], J [ i ] = map ( int , input () . split ())
if i > 0 :
A = C [ i ] - C [ i - 1 ]
B = J [ i ] - J [ i - 1 ]
if A <= 0 and B <= 0 :
fail = True
elif B > 0 and A < 0 : # y/x > (-A)/B if B > 0
if ( - A ) * q0 > p0 * B :
p0 , q0 = - A , B
elif B < 0 and A > 0 : # y/x < A/(-B) if B < 0
if A * q1 < p1 * ( - B ):
p1 , q1 = A , - B
if p0 * q1 >= p1 * q0 or fail :
return "IMPOSSIBLE"
p , q = middle ( p0 , q0 , p1 , q1 )
return str ( q ) + " " + str ( p )
Calkin–Wilf 树 定义 在二叉树中组织连分数的一种更简单的方法是 Calkin–Wilf 树。
通常如下所示:
树的根节点为 。然后,对于数字为 的顶点,其子节点为 和 。
性质 与 Stern–Brocot 树不同,Calkin–Wilf 树不是二叉搜索树,因此不能用于执行有理二叉搜索。
在 Calkin–Wilf 树中,当 时,分数 的直接父节点为 ;当 时,为 。
在 Stern–Brocot 树中使用了收敛的递归。为了得出连分数和 Calkin–Wilf 树之间的联系,应该使用完整商(complete quotients)的递归。如果 ,则 。
另一方面,当 时,在 Calkin–Wilf 树中重复从 到它的父节点,那么将以 结尾。如果继续这样做,将以 ,然后 等结尾。由此可以推断:
当 时,Calkin–Wilf 树中 的直接父节点为 。 当 且 时,其直接父节点为 。 当 且 时,其直接父节点为 。 相应地, 的子节点为
,即 。 ,对于 ,它是 ;对于 ,则是 。值得注意的是,如果以广度优先搜索顺序枚举 Calkin–Wilf 树的顶点(即,根有一个数字 ,而顶点 的子节点有相应的索引 和 ),Calkin–Welf 树中有理数的索引将与 Stern–Brocot 树中的索引相同。
因此,Stern–Brocot 树和 Calkin–Wilf 树的相同级别上的数字是相同的,但它们的排序通过 位反转排列 (bit-reversal permutation)而不同。
Farey 序列 定义 Stern–Brocot 树与 Farey 序列有着极其相似的特征。第 个 Farey 序列记作 ,表示把分母小于等于 的所有最简真分数按大小顺序排列形成的序列。
显然,上述构建 Stern–Brocot 树的算法同样适用于构建 Farey 序列。因为 Stern–Brocot 树中的数是最简分数,因此在边界条件(分母)稍微修改一下就可以形成构造 Farey 序列的代码。可以认为 Farey 序列 是 Stern–Brocot 第 次迭代后得到的序列的子序列。
Farey 序列同样满足最简性和单调性,并且满足一个与 Stern–Brocot 树相似的性质:对于序列中连续的三个数 ,有 。这个可以轻松证明,不再赘述。
由 Farey 序列的定义,可以得到 的长度 公式为:
本页面主要译自博文 Дерево Штерна-Броко. Ряд Фарея 与其英文翻译版 The Stern-Brocot Tree and Farey Sequences 。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。
本页面最近更新:2024/12/14 19:20:48 ,更新历史 发现错误?想一起完善? 在 GitHub 上编辑此页! 本页面贡献者:iamtwz , StudyingFather , 1804040636 , c-forrest , c8ef , caijianhong , CCXXXI , Chrogeek , CookiePieWw , countercurrent-time , diauweb , Enter-tainer , Great-designer , H-J-Granger , HeRaNO , Ir1d , jiang1997 , LLLMMKK , lyccrius , Menci , NachtgeistW , shawlleyw , shuzhouliu , sshwy , SukkaW , TianKong-y , Tiphereth-A , untitledunrevised , Xeonacid , yanboishere , yusancky 本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用