zoukankan      html  css  js  c++  java
  • Heap:左式堆的应用例(任意序列变单调性最小价值)

                  首先来说一下什么是左式堆:

    A:左式堆是专门用来解优先队列合并的麻烦(任意二叉堆的合并都必须重新合并,O(N)的时间)。

      左式堆的性质:

      1.定义零路经长:节点从没有两个两个儿子节点的路经长,把NULL定义为-1

      2.堆性性质(x的键值比x左右两个儿子节点的键值要大或者要小)

      3.堆中的每一个节点x,左儿子的零路经长至少与右儿子的零路经长一样长。

      4.节点的距离等于右节点的距离+1.

      引理:

        若左式堆的距离定义为一定值,则节点数最少的左式堆是完全二叉堆。

      定理:

        若左式堆的距离为k,则这棵树最少有2^(k+1)-1个节点(由引理推出)

    B:左式堆的特殊操作:(Merge)

      左式堆一般以结构体定义,结构体要有3个区域:键值区,零路经长区,左右节点区

      左式堆和二叉堆的其他操作都是类似的,但是其核心操作都是围绕Merge展开的。

      Merge例程:

      

     1 LEFTIST_NODE *Merge1(LEFTIST_NODE *const s1, LEFTIST_NODE *const s2)//以最小堆为例
     2 {
     3     if (s1->left == NULL) s1->left = s2;
     4     else
     5     {
     6         s1->right = Merge2(s1->right, s2);
     7         if (s1->left->zeroh < s1->right->zeroh)//如果违反零节点定理,我们交换节点
     8         {
     9             LEFTIST_NODE *tmp = s1->left;
    10             s1->left = s1->right; s1->right = tmp;
    11         }
    12         s1->zeroh = s1->right->zeroh + 1;//因为这个时候右节点的零节点路经长总是小于左节点的,零点路径长总是取最小的
    13     }
    14     return s1;
    15 }
    16 
    17 LEFTIST_NODE *Merge2(LEFTIST_NODE *const s1, LEFTIST_NODE *const s2)
    18 {
    19     if (s1 == NULL) 
    20         return s2;
    21     else if (s2 == NULL) 
    22         return s1;
    23     else if (s1->elements < s2->elements)
    24         return Merge1(s1, s2);
    25     else//s1->elements < s2->elements
    26         return Merge1(s2, s1);
    27 }

      有了Merge,一切都变得很简单了

      Insert操作:把Insert的值单独列为一个新的节点,然后Merge即可。

      DeleteMin(Max)操作:把根节点的左右子堆合并,并且清除根节点。

    C:左式堆的应用,数字序列(就是POJ 3666)

      POJ 3666那题,可以用左式堆来做。(这一题只用求不下降序列)

      那么怎么做呢?这一题可以这么思考:

      我们把泥土划分为一个一个区间:比如[q[i]-q[i+1]-1],[q[i+1],q[i+2]-2]....这样的话,每一个区间的最小价值,就是区间内的所有值改为该区间的中位数,每个区间的中位数是上升的,即可

      不过这样说有点先入为主了,我们想一下这样做为什么对。

      其实也用不着多严格的证明,我们可以这样看

          我们来看这样一个图,假设一个区间的数的分布就是这样的,其实这个价值点就是到任意蓝色轴的距离,那么这个区间所有点的最小值什么时候到最小?没错,就是到中点的时候。

      把这个结论推广到全序列,那就是保证当每个区间的中位数是递增的,然后最小值就是对应区间的数变成对应中位数所需要的价值之和。

      那么怎么编程呢?左式堆简直就是为这一题设的!

      我们可以弄一个这样的堆,堆的最大值是这个区间的中位数,也就是堆只管理区间的一半(当然要另外开一个区域记录区间的实际大小)。

      我们把每一个节点当做新的堆,如果序列是递增的,我们就把一个一个节点当做区间,并且不断压入栈,如果出现新入栈的节点的数值(也就是新的区间中位数,只有一个节点当然是节点的键值就是中位数了),那么我们就合并堆,直到合并到栈中的根的键值(中位数)是递增即可,同时,我们合并的时候,因为堆的信息只保留区间一半(包括中位数),也就是(len[k]+1)/2,如果出现新的堆和旧的堆合并,且(len[k]+1)/2+(len_new+1)/2>(len[k]+len_new+1)/2的时候(最多新的堆只会比要求大1),那么直接弹出堆的最大根,弹出后的堆根键值刚好就是符合条件的中位数。

      参考:http://blog.csdn.net/iaccepted/article/details/6748038

         http://m.blog.csdn.net/blog/u013595779/44004041

      代码:

      

      1 #include <iostream>
      2 #include <functional>
      3 #include <algorithm>
      4 #define NullNode -1
      5 #define MAX_N 2001
      6 
      7 using namespace std;
      8 
      9 typedef int Position;
     10 typedef struct _leftistheap
     11 {
     12     int value;
     13     Position left, right;
     14     int Npl;
     15 }Left_Heap;
     16 Position Merge1(Position, Position, Left_Heap *);
     17 Position Merge2(Position, Position, Left_Heap *);
     18 
     19 static int len[MAX_N];
     20 static int road[MAX_N];
     21 Position stack[MAX_N];//中点栈组
     22 Left_Heap Increase_Set[MAX_N];
     23 
     24 long long Search(const int, Left_Heap *);
     25 void Swap(Left_Heap *, Position);
     26 
     27 int main(void)//O(nlogn)处理3666
     28 {
     29     int n;
     30     long long ans1;
     31     while (~scanf("%d", &n))
     32     {
     33         memset(Increase_Set, -1, sizeof(Increase_Set));
     34         for (int i = 0; i < n; i++)
     35         {
     36             scanf("%d", &road[i]);
     37             Increase_Set[i].value = road[i];
     38             Increase_Set[i].Npl = 0;
     39         }
     40         ans1 = Search(n, Increase_Set);
     41         printf("%lld
    ", ans1);
     42     }
     43     return 0;
     44 }
     45 
     46 Position Merge1(Position H1, Position H2, Left_Heap *Node_Set)
     47 {
     48     if (H1 == NullNode)
     49         return H2;
     50     else if (H2 == NullNode)
     51         return H1;
     52     else if (Node_Set[H1].value >= Node_Set[H2].value)//注意符号!!!
     53         return Merge2(H1, H2, Node_Set);
     54     else 
     55         return Merge2(H2, H1, Node_Set);
     56 }
     57 
     58 Position Merge2(Position H1, Position H2, Left_Heap *Node_Set)
     59 {
     60     if (Node_Set[H1].left == NullNode)
     61         Node_Set[H1].left = H2;
     62     else
     63     {
     64         Node_Set[H1].right = Merge1(Node_Set[H1].right, H2, Node_Set);
     65         if (Node_Set[Node_Set[H1].left].Npl < Node_Set[Node_Set[H1].right].Npl)
     66             Swap(Node_Set, H1);
     67         Node_Set[H1].Npl = Node_Set[Node_Set[H1].right].Npl + 1;//不能用pos2了,已经变了
     68     }
     69     return H1;
     70 }
     71 
     72 void Swap(Left_Heap *Node_Set, Position x)
     73 {
     74     Node_Set[x].left ^= Node_Set[x].right;
     75     Node_Set[x].right ^= Node_Set[x].left;
     76     Node_Set[x].left ^= Node_Set[x].right;
     77 }
     78 
     79 long long Search(const int n, Left_Heap *Node_Set)
     80 {
     81     memset(len, 0, sizeof(len));
     82     int top = 0, sum_node_tmp, pos, k;
     83     long long ans = 0;
     84 
     85     for (int i = 0; i < n; i++)
     86     {
     87         sum_node_tmp = 1; pos = i;
     88         while (top > 0 && Node_Set[stack[top - 1]].value > Node_Set[pos].value)
     89             //左式堆只储存左半树的信息,也就是以中位数为最大的最大堆
     90         {
     91             pos = Merge1(stack[top - 1], pos, Node_Set);//合并成一棵新的堆,现在新的堆的堆头就是新的区间中位数
     92             if ((len[top - 1] + 1) / 2 + (sum_node_tmp + 1) / 2 > (len[top - 1] + sum_node_tmp + 1) / 2)
     93                 //如果比保留长度大(而且只会大1,则弹出最大节点)
     94                 pos = Merge1(Node_Set[pos].left, Node_Set[pos].right, Node_Set);
     95             sum_node_tmp += len[--top];
     96         }
     97         len[top] = sum_node_tmp;
     98         stack[top++] = pos;
     99     }
    100 
    101     for (int i = 0, j = 0; i < top; i++)
    102     {
    103         k = Node_Set[stack[i]].value;
    104         while (len[i]--)
    105             ans += abs(road[j++] - k);
    106     }
    107     return ans;
    108 }

      这是0(nlogn)的算法,优化程度立竿见影

  • 相关阅读:
    函数的进阶
    几个基础 类型循环删除
    函数的初识
    python3的 基础
    python3 最基础
    demo
    [转] ajax方法
    <codis><jodis>
    <Redis Advance><Pipelining><Memory Optimization><Expire><Transactions>
    <HBase><Scan>
  • 原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/4930596.html
Copyright © 2011-2022 走看看