项目编号:bzoj-1078

项目等级:Safe

项目描述:

  戳这里

特殊收容措施:

  斜堆有一个著名的性质:如果一个节点没有左子树,则它一定没有右子树,这也是它“斜堆”名称的由来。

  此题通过给出斜堆来生成插入序列,正着构造显然很难,我们考虑倒着删,这样只需考虑最后一个插入斜堆的节点的性质即可。

  考虑一个没有右子树的节点,它的左儿子的子树(不含左儿子自己)一定不会是最后一个插入的节点,我们可以运用反证法:

  设该无右子树的节点为P,P的左儿子为L,如果L的子树(不含L)中有一个最后插入的节点,则在插入这个节点前P的左右子树交换,即插入此节点前P有右子树无左子树,违背了斜堆的基本性质,故原命题成立。

  又根据斜堆的插入方法,最后插入的一个节点一定是一个极左节点。

  因此,通常最后插入的节点一定是一个深度最小的极左节点。

  特殊地,如果L为叶子,则插入L前P为叶子,此时L作为最后一个插入的节点是合法的。又根据斜堆的插入方法,L的权值一定大于P,考虑到生成序列字典序最小,选择权值较大的L作为序列中较靠后的节点显然更优。

  总结一下最后一个插入的节点的寻找方法:先找到深度最小的没有右子树的极左节点,看它的左儿子是不是叶子,若是,则目标节点为左儿子,否则为该节点。

  这样,每次寻找最后一个插入的节点从斜堆中倒序删除即可找出字典序最小的生成序列了。复杂度O(nlog2n)。

附录:

 1 #include <bits/stdc++.h>
 2 #define range(i,c,o) for(register int i=(c);i<(o);++i)
 3 #define dange(i,c,o) for(register int i=(c);i>(o);--i)
 4 using namespace std;
 5  
 6 static int n;
 7 int fat[105],son[105][2],ans[105];
 8 #define L(x) son[x][0]
 9 #define R(x) son[x][1]
10  
11 int main()
12 {
13     scanf("%d",&n);
14     memset(fat,-1,sizeof fat);
15     memset(son,-1,sizeof son);
16     range(i,1,n+1)
17     {
18         int x; scanf("%d",&x); bool y=x>99;
19         if(y) x-=100; son[fat[i]=x][y]=i;
20     }
21     int root=0;
22     dange(i,n-1,-1)
23     {
24         int cur=root; for(;~R(cur);cur=L(cur));
25         if(~L(cur)&&!~L(L(cur))) cur=L(cur);
26         int fa=fat[cur],ls=L(cur);
27         ~fa?L(fa)=ls:root=ls; if(~ls) fat[ls]=fa;
28         for(;~fa;fa=fat[fa]) swap(L(fa),R(fa));
29         ans[i]=cur;
30     }
31     printf("%d",root);
32     range(i,0,n) printf(" %d",ans[i]);
33     return 0;
34 }
View Code