bzoj 1078 [SCOI2008] 斜堆
题目传送门
题目大意
给定一个(小根)斜堆的生成方式。
- 如果$H$为空,或者插入的数$x$的权值小于根节点的权值,那么用$x$顶替$H$的位置,然后把$H$作为它的左子树。
- 否则交换$H$根的左右子树,然后递归左子树。
给定一个斜堆,元素大小分别为$0, 1, \dots, n$,问字典序最小的插入序列。
(这篇随笔可能不太严谨。)
考虑倒着做这么一个操作。不难发现下面这两条:
引理1 斜堆$H$中所有非叶节点必然存在左子树。
证明 假设存在一个非叶节点$x$不存在左子树,那么它一定只存在右子树。
显然它不可能比它右子树中某个点$y$晚插入,因为插入$y$的时候,原来的右子树变成了左子树,之后一定存在。
同时它也不可能比它右子树中某个点$y$之前插入,因为插入$x$的时候,$y$会在$x$的左子树中,再次插入后面的点的时候,左子树一定存在。
因此它不存在右子树。与它是非叶节点矛盾。
然后不难得到:
定理2.1 当新插入的点是非叶节点的时候,它的所有父节点都有右子树,当新插入的点是叶节点的时候
定理2.2 当新插入的点是叶节点的时候,它的父节点的所有父节点都有右子树。
证明 设新插入的点为$x$。
- 当$x$不是叶节点的时候,它的父节点存在同时存在左右子树。假设它的$k$级祖先存在,并同时存在左右子树,当$k + 1$级祖先存在的时候,推得$k + 1$级祖先存在右子树,所以$k + 1$级祖先同时存在左右子树。因此定理2.1得证。
- 当$x$是叶节点的时候,它的父节点原本是叶节点或只存在左子树。剩下的一样。
然后再讨论一下还有哪些特点
定理3 新插入的点必定满足:
- 在最左链上
- 不存在右子树
这两点都比较显然。由插入做法易证。
定理4 一个二叉树$H$能通过斜堆车插入方式得到的充分必要条件是:
- 满足堆性质
- 要么是叶节点,要么存在左子树。
证明 必要性 第一点显然,第二点由引理1可证。
充分性 当点数为1的时候,显然成立。假设当点数不超过$k$时成立,考虑当点数为$k + 1$的时候。
当根节点右子树为空,因为左子树是能够构造出来,可以先建出左子树,然后再插入根节点。
否则我们考虑当前最后一个插入的点,由定理3可以知道它存在于当前根的左子树中。
一个插入序列在满足根节点同时存在左右子树之后,只是交替着向两个子树中插入元素。
假设知道了左子树和右子树的插入序列(根据归纳假设我们知道它是存在的)。
显然插入操作是可逆的,我们交替着撤销左右子树的插入,直到根没有同时存在两棵子树,这时候根一定不存在右子树(删完最后一个点,然后交换左右子树,所以右子树为空)。
此时把根删掉。然后可以继续按照左子树的插入序列构造剩下的左子树。
然后我们就倒着构造出了插入序列。
定理5 一个点能成为最后一个插入的点的充分必要条件是:
- 在最左链上
- 不存在右子树
- 当是叶节点时,父节点的所有父节点存在右子树,当是非叶节点时,它的所有父节点存在右子树
证明 必要性由以上讨论可知。
充分性 由定理4必要性可知,每个非叶点均存在左子树并且满足堆性质。
当它是叶节点的时候把它删掉,然后交换它所有的祖先的左右子树。它的父节点要么变成叶节点要么只存在左子树。因为它父节点的祖先均在右子树,所以左子树非空。其他点不受影响,所以所有非叶节点存在左子树。显然仍然满足堆性质。由定理4可知剩下的树能够通过斜堆的插入方法得到。
当把这个点的左子树变为它的父节点的左子树,然后把这个点删掉后,仍然满足堆性质。类似地可以证明。
我们仔细发现,可能成为最后一个插入的点只至多可能有2个。当最左链的叶节点的父节点满足时存在两个,否则只存在一个。
为了使得字典序最小,我们考虑讨论第一种情况。无论发现最后一个是哪一个点,撤销它的插入后,剩下的树的形态都一样。
所以对于其中一个的合法插入序列,交换这两个数的位置,仍然成立。
所以一定是后插入较大的数。(不然我就交换这两个数,然后可以得到字典序更小的插入序列)。
然后做法就变得异常简单:
- 找到最左链上深度最大的满足条件的点
- 删掉它,加入到插入序列,然后交换它的所有父节点的左右子树。
Code
1 /** 2 * bzoj 3 * Problem#1078 4 * Accepted 5 * Time: 4ms 6 * Memory: 1304k 7 */ 8 #include <iostream> 9 #include <cstdlib> 10 #include <cstdio> 11 using namespace std; 12 typedef bool boolean; 13 14 const int N = 1e3 + 5; 15 16 int n; 17 int ch[N][2]; 18 int fa[N]; 19 20 inline void init() { 21 scanf("%d", &n); 22 fa[0] = -1; 23 for (int i = 1, d; i <= n; i++) { 24 scanf("%d", &d); 25 if (d < 100) 26 fa[i] = d, ch[d][0] = i; 27 else 28 d -= 100, fa[i] = d, ch[d][1] = i; 29 } 30 } 31 32 int res[N]; 33 inline void solve() { 34 int m = n; 35 int rt = 0; 36 while (n) { 37 int p = rt; 38 while (ch[p][1]) 39 p = ch[p][0]; 40 int s = ch[p][0]; 41 if (!ch[s][0]) 42 p = s; 43 res[n--] = p; 44 45 int x = fa[p]; 46 if (ch[p][0]) 47 fa[ch[p][0]] = fa[p]; 48 if (x >= 0) { 49 ch[x][0] = ch[p][0]; 50 while (~x) { 51 swap(ch[x][0], ch[x][1]); 52 x = fa[x]; 53 } 54 } else 55 rt = ch[p][0]; 56 } 57 res[0] = rt; 58 for (int i = 0; i <= m; i++) 59 printf("%d ", res[i]); 60 } 61 62 int main() { 63 init(); 64 solve(); 65 return 0; 66 }