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];
}

递归版本

#include <iostream>
#include <cstring>
using namespace std;
const int N = 105;

int data[N];
int dp[N][N];
int sum[N];

int get_sum(int l, int r) {
	return sum[r] - sum[l] + data[l];
}

int solve(int l, int r) {
	if (dp[l][r] != -1) return dp[l][r];
	int tmp_ans = 0, Ans = 0x7fffffff;
	for (int i = l; i < r; ++i) {
		tmp_ans = solve(l, i) + solve(i + 1, r) + get_sum(l, r);
		Ans = min(Ans, tmp_ans);
	}
	return Ans;
}

int main()
{
	memset(dp, -1, sizeof(dp));
	int n;
	cin >> n;
	for (int i = 0; i < n; ++i) {
		cin >> data[i];
		dp[i][i] = 0;
		if (i > 0) {
			sum[i] = sum[i - 1] + data[i];
		} else sum[i] = data[i];
	}
	int ans = solve(0, n - 1);
	cout << "ans = " << ans << endl;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值