201612-4 压缩编码 传送门
想了很久, 还是没有独立做出来.
虽然从问题中抽象出来了是一个什么样的问题.
- 下面是自己想的思路
-
如果这棵树一定要保持叶子节点字典序递增的话,前序遍历的顺序就是字典序.
那么一个结点(按顺序输入的),只能和相邻的结点合并.
合并n - 1次之后, 结果就是一棵维持字典序递增的树的长度.
但是这种合并有很多种情况,答案就是所有情况里面结果最小的.
但是怎么用多项式级别的算法我没有想出来, 用的暴力解法也只得了10分.
搜索以下, 原来这是相邻石子合并问题. 我抽象出来的问题是对的.
我最开始以为的石子合并问题是任意选择两个堆, 而不是只能与相邻的堆合并. 事实上, 他们都是石子合并问题, 不过后面的是前面的进阶版. 还有一个更复杂的石子合并问题是这些石子堆是环形的, 那就更复杂了.
第一种情况就是基本算是哈夫曼编码的简化版, 是贪心算法.
第二种情况贪心是行不通的, 而要用DP.
大事化小, 小事化了.
要求1...n
这个线性堆, 只要求出以其中某个点为根节点的最优子树组合的最佳情况. 这样, 1...n
这个问题就编程1...k
和k+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];
}