【学习笔记】差分约束系统+可并堆

这两个东西没什么关系,纯属凑篇幅

差分约束系统

差分约束系统就是给出一些形如x-y<=w不等式的约束,问你是否有满足问题的解,或者求最小,最大解。

这个问题的神奇之处是可以转化为图论的最短路问题。

所以我们需要用到的知识:
最短路径(主要是spfa)

================分割线================

其实看到不等式,我们也可以想起最短路中的一个叫松弛操作的东西:

d[u]+w(u,v)<d[v] d [ u ] + w ( u , v ) < d [ v ]

对比上面的不等式,两个不等式的不等号正好相反,但是再仔细一想,其实它们的逻辑是一致的,因为SPFA的松弛操作是在满足小于的情况下进行松弛,所以我们可以将每个不等式转化成图上的有向边:

对于每个不等式 x[i]x[j]<=a[k] x [ i ] − x [ j ] <= a [ k ] ,对结点 j j i i 建立一条 j>i j − > i 的有向边,边权为 a[k] a [ k ] ,求 x[n1]x[0] x [ n − 1 ] − x [ 0 ] 的最大值就是求 0 0 n1 n − 1 的最短路。
当然,如果求最小值呢?看下面啦:

关于建图:
如果给出的不等式有”<=”也有”>=”,又该如何解决呢?
很明显,首先需要关注最后的问题是什么,如果需要求的是两个变量差的最大值,那么需要将所有不等式转变成”<=”的形式,建图后求最短路;相反,如果需要求的是两个变量差的最小值,那么需要将所有不等式转化成”>=”,建图后求最长路。

如果有形如:A - B = c 这样的等式呢?我们可以将它转化成以下两个不等式:

A - B >= c (1)
A - B <= c (2)

再通过上面的方法将其中一种不等号反向,建图即可。
最后,如果这些变量都是整数域上的,那么遇到A - B < c这样的不带等号的不等式,我们需要将它转化成”<=”或者”>=”的形式,即 A - B <= c - 1。

大概就是这样的了。
例题很多,比如最近我做的CCPC-FINAL里面就有一道。
一般来说比较裸吧。

==============分割线==================

可并堆

关于可并堆,只简单学习了左偏树和斜堆。
fib堆看起来比较复杂,以后再系统学习吧。
学习资料可以看《左偏树的特点及其应用》
不想下载的看这个博客,博主是转载的。

===============分割线================

左偏树

由于我不是很想写自己的理解,贴一下文章里的吧。
以下均为转载

关于左偏树的性质

[性质1] 节点的键值小于或等于它的左右子节点的键值。

key(i)key(parent(i)) k e y ( i ) ≤ k e y ( p a r e n t ( i ) ) 这条性质又叫堆性质。符合该性质的树是堆有序的 (HeapOrdered) ( H e a p − O r d e r e d ) 。有了性质1,我们可以知道左偏树的根节点是整棵树的最小节点,于是我们可以在 O(1) O ( 1 ) 的时间内完成取最小节点操作。

[性质2] 节点的左子节点的距离不小于右子节点的距离。

dist(left(i))dist(right(i)) d i s t ( l e f t ( i ) ) ≥ d i s t ( r i g h t ( i ) ) 这条性质称为左偏性质。性质2是为了使我们可以以更小的代价在优先队列的其它两个基本操作(插入节点、删除最小节点)进行后维持堆性质。在后面我们就会看到它的作用。

这两条性质是对每一个节点而言的,因此可以简单地从中得出,左偏树的左右子树都是左偏树。

由这两条性质,我们可以得出左偏树的定义:左偏树是具有左偏性质的堆有序二叉树。

[性质3] 节点的距离等于它的右子节点的距离加1。

dist(i)=dist(right(i))+1 d i s t ( i ) = d i s t ( r i g h t ( i ) ) + 1 外节点的距离为0,由于性质2,它的右子节点必为空节点。为了满足性质3,故前面规定空节点的距离为-1。

我们的印象中,平衡树是具有非常小的深度的,这也意味着到达任何一个节点所经过的边数很少。左偏树并不是为了快速访问所有的节点而设计的,它的目的是快速访问最小节点以及在对树修改后快速的恢复堆性质。从图中我们可以看到它并不平衡,由于性质2的缘故,它的结构偏向左侧,不过距离的概念和树的深度并不同,左偏树并不意味着左子树的节点数或是深度一定大于右子树。

下面我们来讨论左偏树的距离和节点数的关系。

[引理1] 若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树。

证明:由性质2可知,当且仅当对于一棵左偏树中的每个节点i,都有 dist(left(i))=dist(right(i)) d i s t ( l e f t ( i ) ) = d i s t ( r i g h t ( i ) ) 时,该左偏树的节点数最少。显然具有这样性质的二叉树是完全二叉树。

[定理1] 若一棵左偏树的距离为k,则这棵左偏树至少有 2k+11 2 k + 1 − 1 个节点。

证明:由引理1可知,当这样的左偏树节点数最少的时候,是一棵完全二叉树。距离为k的完全二叉树高度也为k,节点数为 2k+11 2 k + 1 − 1 ,所以距离为k的左偏树至少有 2k11 2 k + 1 − 1 个节点。

作为定理1的推论,我们有:

[性质4] 一棵N个节点的左偏树距离最多为 ëlog(N+1)û1 ë l o g ( N + 1 ) û − 1 。

证明:设一棵N个节点的左偏树距离为k,由定理1可知, N2k+11 N ≥ 2 k + 1 − 1 ,因此 këlog(N+1)û1 k ≤ ë l o g ( N + 1 ) û − 1

左偏树的合并

CMerge(A,B) C ← M e r g e ( A , B )

Merge()A,BCAB M e r g e ( ) 把 A , B 两 棵 左 偏 树 合 并 , 返 回 一 棵 新 的 左 偏 树 C , 包 含 A 和 B 中 的 所 有 元 素 。 在 本 文 中 , 一 棵 左 偏 树 用 它 的 根 节 点 的 指 针 表 示 。

NULL 在 合 并 操 作 中 , 最 简 单 的 情 况 是 其 中 一 棵 树 为 空 ( 也 就 是 , 该 树 根 节 点 指 针 为 N U L L ) 。
这时我们只须要返回另一棵树。

AB A 和 B 都非空,我们假设 A A 的根节点小于等于 B B 的根节点(否则交换 A,B A , B ),把 A A 的根节点作为新树 C C 的根节点,剩下的事就是合并A的右子树 right(A)B r i g h t ( A ) 和 B 了。

right(A)Merge(right(A),B) r i g h t ( A ) ← M e r g e ( r i g h t ( A ) , B )

right(A)Bright(A)right(A)left(A)2left(A)right(A) 合 并 了 r i g h t ( A ) 和 B 之 后 , r i g h t ( A ) 的 距 离 可 能 会 变 大 , 当 r i g h t ( A ) 的 距 离 大 于 l e f t ( A ) 的 距 离 时 , 左 偏 树 的 性 质 2 会 被 破 坏 。 在 这 种 情 况 下 , 我 们 只 须 要 交 换 l e f t ( A ) 和 r i g h t ( A ) 。

dist(left(A))>dist(right(A))left(A)right(A) 若 d i s t ( l e f t ( A ) ) > d i s t ( r i g h t ( A ) ) , 交 换 l e f t ( A ) 和 r i g h t ( A )

最后,由于 right(A) r i g h t ( A ) 的距离可能发生改变,我们必须更新A的距离:

dist(A)dist(right(A))+1 d i s t ( A ) ← d i s t ( r i g h t ( A ) ) + 1

不难验证,经这样合并后的树C符合性质1和性质2,因此是一棵左偏树。至此左偏树的合并就完成了。

插入新节点

单节点的树一定是左偏树,因此向左偏树插入一个节点可以看作是对两棵左偏树的合并。下面是插入新节点的代码:

Procedure Insert(x, A)

   B ← MakeIntoTree(x)

   A ← Merge(A, B)

End Procedure

由于合并的其中一棵树只有一个节点,因此插入新节点操作的时间复杂度是 O(logn) O ( l o g n )

删除最小节点

由性质1,我们知道,左偏树的根节点是最小节点。在删除根节点后,剩下的两棵子树都是左偏树,需要把他们合并。删除最小节点操作的代码也非常简单:

Function DeleteMin(A)

   t ← key(root(A))

   A ← Merge(left(A), right(A))

   return t

End Function

由于删除最小节点后只需进行一次合并,因此删除最小节点的时间复杂度也为 O(logn) O ( l o g n )

左偏树的构建

将n个节点构建成一棵左偏树,这也是一个常用的操作。

【算法一 】 暴力算法——逐个节点插入,时间复杂度为 O(nlogn) O ( n l o g n )

【算法二 】 仿照二叉堆的构建算法,我们可以得到下面这种算法:

  • 将n个节点(每个节点作为一棵左偏树)放入先进先出队列。
  • 不断地从队首取出两棵左偏树,将它们合并之后加入队尾。
  • 当队列中只剩下一棵左偏树时,算法结束。

时间复杂度 O(n) O ( n )

==============分割线=================

关于斜堆

这是一个每次合并都交换左右孩子的算法。
合并大概就是:

  • 如果一个空斜堆与一个非空斜堆合并,返回非空斜堆。
  • 如果两个斜堆都非空,那么比较两个根节点,取较小堆的根节点为新的根节点。将”较小堆的根节点的右孩子”和”较大堆”进行合并。
  • 合并后,交换新堆根节点的左孩子和右孩子。

总之个人感觉很玄学。
好fake的数据结构啊

你可能感兴趣的:(学习知识up,学习笔记)