[Daimayuan] 切割(C++,贪心,哈夫曼树)

题目描述

有一个长度为 ∑ a i \sum a_i ai 的木板,需要切割成 n n n 段,每段木板的长度分别为 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an

每次切割,会产生大小为被切割木板长度的开销。

请你求出将此木板切割成如上 n n n 段的最小开销。

输入格式

1 1 1 行一个正整数表示 n n n

2 2 2 行包含 n n n 个正整数,即 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an

输出格式

输出一个正整数,表示最小开销。

样例输入
5
5 3 4 4 4
样例输出
47
数据范围

对于全部测试数据,满足 1 ≤ n , a i ≤ 1 0 5 1≤n,a_i≤10^5 1n,ai105

附加说明

原题:[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

需要 O(n) 解法的 数据加强版 1 ≤ n ≤ 1 0 7 1≤n≤10^7 1n107

解题思路

首先我们采用逆向思维,改变题意为:

n n n块长度分别为 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an的木板合并成一块,每次合并只能操作两块,会产生合并后木板长度的开销。

可以很容易发现这和题意描述是一样的。

然后引入核心思路:哈夫曼树

哈夫曼树常用于数据压缩(本质上是编码方式),基本思想就是:

统计文本中的所有符号的词频,每次选择词频最低的两个进行操作,将它们连到一个新的父节点上,然后将父节点赋值为二者词频之和,直到生成一棵树。

这种方式能够保证词频越小的节点深度越深(编码长度越长),词频越高的节点深度越浅(编码长度越短),也就完成了数据压缩。

那么这和本题有什么关系?

我们可以认为哈夫曼树中的叶子节点就是 n n n块木板,节点深度就是木板被操作的次数。

(注:我们可以把合并后的木板仍然看成是多块木板,只不过这几块木板可以一起操作。)

所以,深度越深也就代表着这块木板被操作的次数越多,深度越浅也就代表着这块木板被操作的次数越少。

故哈夫曼树算法能够保证最小开销。

代码实现:

采用优先队列维护木板,每次取出两块进行合并,然后将合并后的木板插入队列。

AC代码如下:

(前排提示:/* 十年OI一场空,不开long long见祖宗 */

#include <iostream>
#include <queue>
using namespace std;
const int max_n = 1e5;
const int max_a = 1e5;

priority_queue<long long, vector<long long>, greater<long long>>pq;

int main() {
	long long n, temp;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> temp;
		pq.push(temp);
	}

	long long ans = 0;
	while (pq.size() > 1) {
		long long b1 = pq.top(); pq.pop();
		long long b2 = pq.top(); pq.pop();
		pq.push(b1 + b2);
		ans += (long long)(b1) + (long long)(b2);
	}
	cout << ans << endl;
	return 0;
}

后排推一下我写的合并果子 q w q qwq qwq

然后是数据加强版合并果子:

贪心算法是不能优化的了,可以优化的地方在于优先队列。

因为我们每次将合并后的果堆插入队列中,队列都会运行排序算法找到应该插入的位置。

优化的前提是这样的:

每次合并后的果堆一定不会比上一次合并得到的果堆小。

那么我们就不需要将其插入优先队列,只需要另外维护一个队列用来存储合并后的果堆,然后每次取出两个队列中队首比较小的一个即可。

直观的思路是这样的:

首先采用比快速排序更快的排序算法桶排序,将果堆维护在一个有序队列中;

然后再维护一个队列用于存储合并后的果堆;

最后运行的贪心算法与之前一致。

AC代码如下:

#include <iostream>
#include <queue>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int max_a = 1e5;

queue<long long>q1, q2;
int buckets[max_a + 1];

int main() {
	int n, temp;
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", &temp);
		buckets[temp]++;
	}

	for (int i = 1; i <= max_a; i++) {
		while (buckets[i]--) {
			q1.push((long long)(i));
		}
	}

	long long t1, t2, ans = 0;
	for (int i = 1; i < n; i++) {
		if (q2.empty() || !q1.empty() && q1.front() < q2.front()) {
			t1 = q1.front();
			q1.pop();
		}
		else {
			t1 = q2.front();
			q2.pop();
		}

		if (q2.empty() || !q1.empty() && q1.front() < q2.front()) {
			t2 = q1.front();
			q1.pop();
		}
		else {
			t2 = q2.front();
			q2.pop();
		}

		q2.push(t1 + t2);
		ans += t1 + t2;
	}

	cout << ans << endl;
	return 0;
}

其实这段代码还会 T T T,怎么优化?当然是用无敌的快读了:

void read(int& x) {
    x = 0;
    char c = getchar();
    while ('0' <= c && c <= '9') {
        x = x * 10 + c - '0';
    	c = getchar();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
哈夫曼树是一种基于字符频率来构建的最优二叉树,用于压缩和解压缩数据。在 C++ 中,可以通过以下步骤来实现哈夫曼树: 1. 定义结构体存储哈夫曼树节点信息,包括字符、权值、左右子节点等信息。 ``` struct TreeNode { char ch; int freq; TreeNode *left, *right; }; ``` 2. 构建哈夫曼树的主要过程是先将字符按照频率从小到大排序,然后将最小的两个节点合并为一个新节点,其权值为两个节点权值之和。重复此过程直到只剩下一个节点,即为哈夫曼树的根节点。可以使用 STL 中的 priority_queue 来实现节点合并的过程。 ``` struct cmp { bool operator() (TreeNode* a, TreeNode* b) { return a->freq > b->freq; } }; void buildHuffmanTree(vector<char>& chars, vector<int>& freqs) { priority_queue<TreeNode*, vector<TreeNode*>, cmp> pq; for (int i = 0; i < chars.size(); ++i) { TreeNode* node = new TreeNode(chars[i], freqs[i]); pq.push(node); } while (pq.size() > 1) { TreeNode* left = pq.top(); pq.pop(); TreeNode* right = pq.top(); pq.pop(); TreeNode* parent = new TreeNode('#', left->freq + right->freq); parent->left = left; parent->right = right; pq.push(parent); } } ``` 3. 对于给定的字符串,可以通过哈夫曼树得到每个字符的编码,即左子节点对应编码为 0,右子节点对应编码为 1。可以使用递归的方式遍历哈夫曼树,生成编码表。 ``` unordered_map<char, string> generateCodeMap(TreeNode* root) { unordered_map<char, string> codeMap; function<void(TreeNode*, string)> dfs = [&](TreeNode* node, string code) { if (!node) return; if (node->ch != '#') codeMap[node->ch] = code; dfs(node->left, code + "0"); dfs(node->right, code + "1"); }; dfs(root, ""); return codeMap; } ``` 以上就是使用 C++ 实现哈夫曼树的基本步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值