动态规划之石子合并

这类问题是动态规划中经典题型,主要有三种变形。网上关于这个问题的资料很多,我就不赘述了。简单的介绍一下这三种题型。

  • 有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
    这类问题使用贪心算法即可解决,哈夫曼树的变形。每次只要合并最小的两堆,即可保证结果最小(最大)。

  • 链式结构:有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
    这个问题需要使用动态规划求解,如果使用贪心算法求得当前最小值可能不是最优解。如: 7 6 5 7 100
    如果使用贪心算法,合并思路为6+5 =11;7+11 =18;18+7 = 25;25+100 = 125。正确的最优解应该是通过动态规划求得:7+6 = 13;5+7 = 12;13+ 12 = 25; 25+100 = 125; 最后结果应该是13+12+25+125 = 175。

  • 环形结构:在一个圆形操场的四周有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

- 问题二解法:

设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:
这里写图片描述


#include "stdafx.h"
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
using namespace std;

int movebook_list(vector<int> book){
    int length = book.size();
    if (length == 0) return 0;
    if (length == 1) return book[0];
    int mergeTotal = INT_MAX;
    int **dp = new int *[length];//保存i->j合并的最优结果
    int **s = new int *[length];//保存截断点位置
    int **sum = new int *[length];//保存i-j的和
    for (int i = 0; i < length; i++){
        dp[i] = new int[length];
        s[i] = new int[length];
        sum[i] = new int[length];
    }
    for (int i = 0; i < length; i++){
        dp[i][i] = 0;
        s[i][i] = i;
        sum[i][i] = book[i];
        for (int j = i + 1; j < length; j++){
            sum[i][j] = sum[i][j-1] + book[j];
        }
    }
    for (int len = 2; len <= length; len++)
    {
        int j;
        for (int i = 0; i < length - len + 1; i++)
        {
            j = (i + len - 1) ;
            dp[i][j] = INT_MAX;
            for (int k = s[i][j-1]; k<=s[i+1][j]&& (k<j);k++)//利用四边形法则进行优化,添加一个截断的标志位s,后面的求解根据之前截断点继续向后搜索以简化求解过程
            {
                    if (dp[i][j]> dp[i][k] + dp[k+1][j] + sum[i][j]){
                        dp[i][j] = dp[i][k] + dp[k+1][j] + sum[i][j];
                        s[i][j] = k;
                    }
            }
        }
    }


    mergeTotal = dp[0][length - 1];
    //打印求解所使用的矩阵
    for (size_t i = 0; i < length; i++)
    {
        int j = 0;
        for (; j < i; j++)
            cout << setw(4) << "";
        for (; j < length; j++)
            cout << setw(4) << left << sum[i][j];
        cout << endl;
    }
    cout << endl;

    for (size_t i = 0; i < length; i++)
    {
        int j = 0;
        for (; j < i; j++)
            cout << setw(4) << "";
        for (; j < length; j++)
            cout << setw(4) << left << dp[i][j];
        cout << endl ;
    }
    cout << endl;
    for (size_t i = 0; i < length; i++)
    {
        int j = 0;
        for (; j < i; j++)
            cout << setw(4) << "";
        for (; j < length; j++)
            cout << setw(4) << left << s[i][j];
        cout << endl;
    }
    cout << endl;
    for (int i = 0; i < length; i++)
    {
        delete[] dp[i];
        delete[] sum[i];
        delete[] s[i];
    }
    delete[] dp;
    delete[] sum;
    delete[] s;

    return mergeTotal;
}


int main()
{
    int N = 0;
    vector <int> b = { 7,6,5,7,100};
    while (cin >> N){
        cout << movebook_list(b);
        cout << endl;
    }
    return 0;
}
- 问题三解法:

状态转移公式:

这里写图片描述

i<=k<j

int movebook_circle(vector<int> book){
    int length = book.size();
    if (length == 0) return 0;
    if (length == 1) return book[0];
    int mergeTotal = INT_MAX;
    int **dp = new int *[length];
    int **s = new int *[length];
    int **sum = new int *[length];
    for (int i = 0; i < length; i++){
        dp[i] = new int[length];
        s[i] = new int[length];
        sum[i] = new int[length];
    }
    for (int i = 0; i < length; i++){
        dp[i][i] = 0;
        s[i][i] = i;
        sum[i][i] = book[i];
        for (int j = i + 1; j < length + i; j++){
            sum[i][j%length] = sum[i][(j - 1) % length] + book[j%length];
        }
    }
    for (int len = 2; len <= length; len++)
    {
        int j;
        for (int i = 0; i < length; i++)
        {
            j = (i + len - 1) % length;
            dp[i][j] = INT_MAX;
            for (int k = i; k<i + len - 1; k++)
            {
                if (dp[i][j]> dp[i][k%length] + dp[(k + 1) % length][j] + sum[i][j])
                    dp[i][j] = dp[i][k%length] + dp[(k + 1) % length][j] + sum[i][j];
            }
        }
    }

    for (int j = 0; j < length; j++)
    {
        if (dp[(j + 1) % length][j] < mergeTotal)
            mergeTotal = dp[(j + 1) % length][j];
    }

    for (size_t i = 0; i < length; i++)
    {
        int j = 0;
        for (; j < length; j++)
            cout << setw(4) << left << sum[i][j];
        cout << endl;
    }
    cout << endl;
    for (size_t i = 0; i < length; i++)
    {
        int j = 0;
        for (; j < length; j++)
            cout << setw(4) << left << dp[i][j];
        cout << endl;
    }
    cout << endl;

    for (int i = 0; i < length; i++)
    {
        delete[] dp[i];
        delete[] sum[i];
        delete[] s[i];
    }
    delete[] dp;
    delete[] sum;
    delete[] s;

    return mergeTotal;
}
int main()
{
    int N = 0;
    vector <int> b = { 7,6,5,7,100};
    while (cin >> N){

        //cout << movebook_list(b);
        //cout << endl;
        cout << movebook_circle(b);
        cout << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值