1.1 搬家1

实验任务

小明的家具包含 \(n\) 个箱子,每个箱子有各自的重量。由于绳子长度有限,他每次只能把最多 \(k\)个箱子绑在一起形成一个新箱子,新箱子的重量为被绑的箱子重量之和。这样绑一次,小明耗费的体力值是被绑的箱子重量之和(即新箱子的重量)。并且新箱子还可以作为一个箱子参与后续的操作。

小明想经过若干次操作,最终把所有的箱子打包成一个箱子。那么他耗费的总体力值最小是多少?

例如,假设一开始有 3 个箱子,重量为 1、2、4,\(k=2\)。那么可以先把 24 绑在一起形成一个重量为 6 的箱子,再把它和重量为 1 的箱子绑在一起,耗费的总体力值是\((2+4)+(6+1)=13\)。也可以先绑 1 和 4 得到 5,再绑 2 和 5,耗费的总体力值是\((1+4)+(2+5)=12\),后一种方案耗费的体力值更小,但这也不是最优的绑法。

数据输入

输入第一行为两个正整数 \(n\), \(k\),含义如上。接下去 \(n\) 个正整数,表示最开始 \(n\) 个箱子的重量,以空格隔开。

数据输出

输出一行,表示最小总花费。

输入示例 输出示例
3 2
1 2 4
10
5 3
1 1 1 1 1
8

数据范围

最开始 \(n\) 个箱子的重量均在\([1, 1000]\)内;\(50\%\)的得分点,\(n\leq 3\)\(k=2\);其余 \(30\%\)的得分点,\(n\leq 100000\)\(k=2\);其余 \(20\%\)的得分点,\(n, k\leq 100000\)\(k\geq 2\)

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <queue>
using namespace std;
#define maxn 100005

int main(){
int n, k;
scanf("%d%d", &n, &k);
int total = 0;
priority_queue<int>pq;
int boxi;
for (int i = 0; i < n; ++i){
scanf("%d", &boxi);
pq.push(-boxi);
}
//添加节点,直到满足k叉哈夫曼树的定义
while ((n - 1) % (k - 1)){
pq.push(0);
++n;
}
while (pq.size()>1){
int temp = 0;
for (int i = 0; i < k; ++i){
temp += pq.top();
pq.pop();
}
total += temp;
pq.push(temp);
}
printf("%d\n", -total);
return 0;
}

设计思路与复杂度分析

\(k\)叉哈夫曼树需要满足的性质:保证每一个非页节点都有\(k\) 个子节点。

如何保证节点页节点数满足性质?考虑特殊情况:第一次\(k\)个叶子节点计算,其余的都是\((k-1)\)个叶子节点与刚才生成的分支节点计算。

反过来考虑:假设第一次也是\((k-1)\)个节点参与计算,这样相当于总数少了1,变成了\(n-1\)个节点每次\((k-1)\)的独立运算!所以当\((n-1)\%(k-1)=0\)时,刚好构造一棵\(k\)叉树。

对每一个箱子需要插入优先队列然后从优先队列取出(只是计算复杂度无需考虑生成的中间节点),所以复杂度为:\(O(nlogn)\)

0%