CCF 201612-4 压缩编码_石子合并问题_DP

201612-4 压缩编码 传送门

想了很久, 还是没有独立做出来.

虽然从问题中抽象出来了是一个什么样的问题.

下面是自己想的思路

如果这棵树一定要保持叶子节点字典序递增的话,前序遍历的顺序就是字典序.
那么一个结点(按顺序输入的),只能和相邻的结点合并.
合并n - 1次之后, 结果就是一棵维持字典序递增的树的长度.
但是这种合并有很多种情况,答案就是所有情况里面结果最小的.

但是怎么用多项式级别的算法我没有想出来, 用的暴力解法也只得了10分.

搜索以下, 原来这是相邻石子合并问题. 我抽象出来的问题是对的.

我最开始以为的石子合并问题是任意选择两个堆, 而不是只能与相邻的堆合并. 事实上, 他们都是石子合并问题, 不过后面的是前面的进阶版. 还有一个更复杂的石子合并问题是这些石子堆是环形的, 那就更复杂了.

第一种情况就是基本算是哈夫曼编码的简化版, 是贪心算法.

第二种情况贪心是行不通的, 而要用DP.

大事化小, 小事化了.

要求1...n这个线性堆, 只要求出以其中某个点为根节点的最优子树组合的最佳情况. 这样, 1...n这个问题就编程1...kk+1...n这个子问题.

我们先求出1个堆组合的最佳情况, 然后据此推出两个堆组合, 然后通过两个堆推出三个堆的, 以此类推, 知道n个堆.

给出状态转移方程

这里写图片描述

因为要先知道数量更小的堆的最优解, 所以最外层遍历是堆的数量. 由三重循环.

话说, CCF第四题, 用20+行的代码就可以拿100分, 这样真的好吗?

#include <iostream>
#include <cstring>
using namespace std;

const int maxn = 1005;
int dp[maxn][maxn] = {};
int sum[maxn] = {}, num[maxn];

int main()
{
    memset(dp, 0x3f, sizeof(dp));
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> num[i];
        sum[i] = num[i] + sum[i - 1];
        dp[i][i] = 0;
    }
    for (int k = 1; k < n; ++k) {
        for (int i = 1, j; i + k <= n; ++i) {
            int su = sum[j = i + k] - sum[i - 1];
            for (int u = i; u < j; ++u) 
                dp[i][j] = min(dp[i][j], dp[i][u] + dp[u+1][j] + su);
        }
    }
    cout << dp[1][n];
}

忍不住压了压行, 虽然有些不择手段, 但是还是蛮有层次的哦, 逻辑也清晰, 只有11行

#include <iostream>
int dp[1005][1005] = {}, sum[1005] = {}, num[1005], n = 0;
int main() {
    for (int i = 0; i <= n; ++i)
        i == 0 ? std::cin >> n : std::cin >> num[i], sum[i] = num[i] + sum[i - 1];
    for (int k = 1; k < n; ++k)
        for (int i = 1, j, su; i + k <= n; ++i)
            for (int u = i, su = sum[j = i + k] - sum[i - 1]; u < j; ++u)
                dp[i][j] = dp[i][j] == 0 ? dp[i][u] + dp[u+1][j] + su : std::min(dp[i][j], dp[i][u] + dp[u+1][j] + su);
    std::cout << dp[1][n];
}

猜你喜欢

转载自blog.csdn.net/wjh2622075127/article/details/81486980