zoukankan      html  css  js  c++  java
  • 石子合并问题

    类型1

    有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新的一堆石子的数量。设计一个算法,将这N堆石子合并成一堆的总花费最小(或最大)。  

    此类问题比较简单,就是哈夫曼编码的变形,用贪心算法即可求得最优解。即每次选两堆最少的,合并成新的一堆,直到只剩一堆为止。证明过程可以参考哈夫曼的证明过程。

    huffman的开销就是:

    cost of tree = Σ freq(i) * depth(i)

    There is another way to write this cost function that is very helpful. Although we are only given frequencies for the leaves, we can define the frequency of any internal node to be the sum of the frequencies of its descendant leaves; this is, after all, the number of times the internal node is visited during encoding or decoding…The cost of a tree is the sum of the frequencies of all leaves and internal nodes, except the root. 

    这样子的话,整个huffman的定义就和题中所说的一样了。

    huffman建编码树,用最小堆是O(nlgn),还有用两个队列来建树的,只需要O(n)。

    变形

    有n堆石子,每堆1个,要合并成一堆,规定每次可以任意选两堆合并成新的一堆,两堆中较少的石子数记为该次合并的得分。

    算法同样可以类比哈夫曼编码来证明。选最小的两个,然后再取最小插入到原数组中。

    出自这里,也提到一种O(n)的算法,看不懂。

    类型2

    在一条直线上摆着N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为将的一堆石子的数量。设计一个算法,将这N堆石子合并成一堆的总花费最小(或最大)。

    递推式为:

    dp[i][j]=min{dp[i][k]+dp[k+1][j]+sum[i][j]}, $i le k < j$;  sum[i][j]是从第i堆(包含)到第j堆(包含)的总石子数。

    求sum[i][j]可以通过O(n)预处理后在O(1)时间内获取。计算状态需要$n^2$,计算每一步最小值需要O(n),所以总共需要$O(n^3)$。

     1 int minStone(int arr[], int n) {
     2     if (n <= 0) return 0;
     3     vector<int> sum(n, 0);
     4     sum[0] = arr[0];
     5     for (int i = 1; i < n; ++i) {
     6         sum[i] = sum[i - 1] + arr[i];
     7     }
     8 
     9     vector<vector<int> > dp(n, vector<int>(n, INT_MAX));
    10     for (int j = 0; j < n; ++j) { 
    11         dp[j][j] = 0;
    12         for (int i = j - 1; i >= 0; --i) {
    13             for (int k = i; k < j; ++k) {
    14                 int v = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i] + arr[i];
    15                 if (v < dp[i][j]) dp[i][j] = v;
    16             }
    17         }
    18     }
    19     return dp[0][n - 1];
    20 }

    类型3

    在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小总得分。

    环形数组的处理和上面类似,可以考虑的是复制一遍原数组,把复制的部分放在原数组末端。

    这里最终要求的就是长度为n的最小值。dp的过程和前面类似,但是返回值就是长度为n的区间的最小值。

     1 int minStoneInCircle(int arr[], int n) {
     2     if (n <= 0) return 0;
     3     int newLen = n * 2 - 1; // special treatment for circular array
     4     vector<int> sum(newLen, 0);
     5     sum[0] = arr[0];
     6     for (int i = 1; i < newLen; ++i) {
     7         sum[i] = sum[i - 1] + arr[i % n];
     8     }
     9 
    10     vector<vector<int> > dp(newLen, vector<int>(newLen, INT_MAX));
    11     int min = INT_MAX;
    12     for (int j = 0; j < newLen; ++j) { 
    13         dp[j][j] = 0; 
    14         for (int i = j - 1; i >= 0 && i >= j - n + 1; --i) {
    15             for (int k = i; k < j; ++k) {
    16                 int v = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i] + arr[i % n];
    17                 if (v < dp[i][j]) dp[i][j] = v;
    18             }
    19         }
    20         // only consider the subarray whose len is n 
    21         if (j >= n - 1 && dp[j - n + 1][j] < min) min = dp[j - n + 1][j];
    22     }
    23     return min;
    24 }

    这里主要是在前面的代码基础上修改。实际上,像这种指定区间长度为n的dp,可以用区间长度作为状态,显得更自然些。复杂度同样是$O(n^3)$。

    优化

    网上查到的,利用四边形不等式,可以优化到$O(n^2)$。

    当函数w[i,j]满足$w[i,j]+w[i',j'] le w[i,j']+w[i',j],i le i' le j le j'$时,称w满足四边形不等式。 就是交叉区间求和小于等于包含区间求和。

    当函数w[i,j]满足$w[i’,j] le w[i,j’], i le i' le j le j'$时称w关于区间包含关系单调。就是被包含区间小于等于包含区间。

    对于石子合并问题,递推式为dp[i][j]=min{dp[i][k]+dp[k+1][j]+sum[i][j]}, $i le k < j$;  

    sum[i][j]就满足四边形不等式和区间包含关系单调。

    从直觉上想,dp[i][j]是从子区间计算而来的,子区间满足四边形不等式和包含关系单调性,在子区间上求和再求最小值也同样满足四边形不等式和包含关系单调性。因为对于dp[i][j], dp[i+1][j],对于dp[i][j]的任何一个求值过程,dp[i+1][j]对应地总可以找到一个子区间包含它。

    设search[i][j]为dp[i][j]中k的取值,也就是

    search[i][j]=max{k | dp[i][j]=dp[i][k]+dp[k+1][j]+sum[i,j]};

    证dp[i][j]满足四边形不等式就是为了证明search[i][j]符合区间包含单调性。

    同样也是可以证明search[i][j]是区间单调的。所以

    $search[i][j-1] le search[i][j] le search[i+1][j]$; 

    所以可以将问题优化为:

    dp[i][j]=min{dp[i][k]+dp[k+1][j]+sum[i][j]}, $search[i][j-1] le k le search[i+1][j]$; 

     1 int minStoneInCircle3(int arr[], int n) {
     2     if (n <= 0) return 0;
     3     int newLen = n * 2 - 1; // special treatment for circular array
     4     vector<int> sum(newLen, 0);
     5     sum[0] = arr[0];
     6     for (int i = 1; i < newLen; ++i) {
     7         sum[i] = sum[i - 1] + arr[i % n];
     8     }
     9 
    10     vector<vector<int> > dp(newLen, vector<int>(newLen, INT_MAX));
    11     vector<vector<int> > search(newLen, vector<int>(newLen, 0));
    12 
    13     int min = INT_MAX;
    14     for (int j = 0; j < newLen; ++j) { 
    15         dp[j][j] = 0; 
    16         search[j][j] = j;
    17         for (int i = j - 1; i >= 0 && i >= j - n + 1; --i) {
    18             for (int k = search[i][j - 1]; k < j && k <= search[i + 1][j]; ++k) {
    19                 int v = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i] + arr[i % n];
    20                 if (v < dp[i][j]) {
    21                     search[i][j] = k;
    22                     dp[i][j] = v;
    23                 }
    24             }
    25         }
    26         // only consider the subarray whose len is n 
    27         if (j >= n - 1 && dp[j - n + 1][j] < min) min = dp[j - n + 1][j];
    28     }
    29     return min;
    30 }

    这个就是$O(n^2)$的了。

  • 相关阅读:
    Django---Django的中间件
    Django---Django的ORM的一对多操作(外键操作),ORM的多对多操作(关系管理对象),ORM的分组聚合,ORM的F字段查询和Q字段条件查询,Django的事务操作,额外(Django的终端打印SQL语句,脚本调试)
    Django---ORM的常用字段和自定义字段,DjangoORM字段与数据库类型对应,字段参数和Meta的参数,Django的admin操作,13中orm操作方法,单标的双下方法
    Django---路由系统,URLconf的配置,正则表达式的说明(位置参数),分组命名(捕获关键字参数),传递额外的参数给视图,命名url和url的反向解析,url名称空间
    Django---CBV和FBV的使用,CBV的流程,给视图加装饰器,Request对象方法,属性和Response对象,form表单的上传
    Django---图书管理系统,多对多(ManyToMany),request获取多个值getlist(),反查(被关联的对象.author_set.all)
    Django---简易图书管理系统(B/S架构)
    从Base64编码转换为图片文件
    图片转化成base64字符串
    设置Linux的一些文本输出方式
  • 原文地址:https://www.cnblogs.com/linyx/p/4008872.html
Copyright © 2011-2022 走看看