Description
在一個操場的四周擺放着n堆石子。現要將石子有次序地合並成一堆。規定每次至少選2 堆最多選k堆石子合並成新的一堆,合並的費用為新的一堆的石子數。試設計一個算法,計算出將n堆石子合並成一堆的最大總費用和最小總費用。
對於給定n堆石子,計算合並成一堆的最大總費用和最小總費用。
Input
輸入數據的第1 行有2 個正整數n和k(n≤100000,k≤10000),表示有n堆石子,每次至少選2 堆最多選k堆石子合並。第2 行有n個數(每個數均不超過 100),分別表示每堆石子的個數。
Output
將計算出的最大總費用和最小總費用輸出,兩個整數之間用空格分開。
Sample
Input
7 3
45 13 12 16 9 5 22
Output
593 199
Hint
請注意數據范圍是否可能爆 int。
題解:
涉及哈夫曼問題,首先哈夫曼樹是每次選擇最大或最小的點搭建的,而大頂堆或小頂堆剛好能滿足這個要求,因此,可以用堆來解哈夫曼問題。
c++中優先隊列便是一個堆,所以可以用優先隊列解題。
由於數據過大,所以int會WA改用long long int。
在建立多元哈夫曼時,如果當前節點不足以建立一個K元的哈夫曼(有一個節點的子節點數量小於K),則需要補零來完善。
每次選擇最大值來建立二元哈夫曼樹,會得到最費力的值。
注:因為考試無法使用優先隊列,因此該代碼只能用於AC題目,不能用作考。后面補了一個不用優先隊列的方法,代碼異常的多,不推薦。
(原來的代碼有bug,雖然能AC,但無法解決n<k的情況,感謝熱心網友NagiRingu)
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define maxn 10050
#define INF 0x3f3f3f3f
using namespace std;
int main(){
int n, k, i, x, sum;
long long MAX, MIN;
MAX = MIN = 0;
cin>> n>> k;
priority_queue<long long> q1;
priority_queue<long long, vector<long long>, greater<long long > > q2;
for(i=0;i<n;i++){
cin>> x;
q1.push(x);
q2.push(x);
}
while((int)q1.size() > 1){
sum = q1.top();
q1.pop();
if(!q1.empty()){
sum += q1.top();
q1.pop();
}
MAX += sum;
q1.push(sum);
}
if(n>k){
x = n;
while(x>k){
x -= (k - 1);
}
for(i=x;i<k;i++){
q2.push(0);
}
}
while((int)q2.size()>k){
sum = 0;
for(i=0;i<k;i++){
sum += q2.top();
q2.pop();
}
MIN += sum;
q2.push(sum);
}
while(!q2.empty()){
MIN += q2.top();
q2.pop();
}
cout<<MAX<<' '<<MIN<<endl;
return 0;
}
不使用優先隊列,只能把堆的代碼寫出來。不過看老師給的參考答案,是可以使用優先隊列的。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define maxn 200050 //多元哈夫曼樹最多會有2n-1個節點。
using namespace std;
//小頂堆建立過程。
struct heap_less{
//數據存儲
long long data[maxn];
//記錄堆的大小。
int num;
//交換下標a,b的位置。
void qswap(int a, int b){
long long t;
t = data[a];
data[a] = data[b];
data[b] = t;
}
//向下調整堆。
void down(int i){
int t, flag = 0;
while(i*2 <= num && flag == 0){
if(data[i] > data[i*2]){
t = i * 2;
}
else
t = i;
if(i * 2 + 1 <= num){
if(data[t] > data[i*2+1]){
t = i * 2 + 1;
}
}
if (t != i){
qswap(i, t);
i = t;
}
else{
flag = 1;
}
}
}
//向上調整堆。
void up(int i){
int t, flag = 0;
while(i >= 1 && flag == 0){
if(data[i] > data[i*2]){
t = i * 2;
}
else
t = i;
if(i * 2 + 1 <= num){
if(data[t] > data[i*2+1]){
t = i * 2 + 1;
}
}
if (t != i){
qswap(i, t);
i = i / 2;
}
else{
flag = 1;
}
}
}
//返回堆頂元素。
int top(){
return data[1];
}
//刪除堆頂元素。
void pop(){
data[1] = data[num];
down(1);
num--;
}
//插入元素。
void push(int x){
num += 1;
data[num] = x;
up(num / 2);
}
};
//大頂堆建立過程,該過程與小頂堆差別不大。
struct heap_gteater{
long long data[maxn];
int num;
void qswap(int a, int b){
long long t;
t = data[a];
data[a] = data[b];
data[b] = t;
}
void down(int i){
int t, flag = 0;
while(i*2 <= num && flag == 0){
if(data[i] < data[i*2]){
t = i * 2;
}
else
t = i;
if(i * 2 + 1 <= num){
if(data[t] < data[i*2+1]){
t = i * 2 + 1;
}
}
if (t != i){
qswap(i, t);
i = t;
}
else{
flag = 1;
}
}
}
void up(int i){
int t, flag = 0;
while(i >= 1 && flag == 0){
if(data[i] < data[i*2]){
t = i * 2;
}
else
t = i;
if(i * 2 + 1 <= num){
if(data[t] < data[i*2+1]){
t = i * 2 + 1;
}
}
if (t != i){
qswap(i, t);
i = i / 2;
}
else{
flag = 1;
}
}
}
int top(){
return data[1];
}
void pop(){
data[1] = data[num];
down(1);
num--;
}
void push(int x){
num += 1;
data[num] = x;
up(num / 2);
}
};
int main()
{
int n, k, i, x;
heap_less h;
heap_gteater h2;
h.num = 0;
scanf("%d%d",&n, &k);
for(i=0; i<n; i++){
scanf("%d",&x);
h.push(x);
h2.push(x);
}
int t = n;
//如果節點數不能夠建立一個完整的哈夫曼樹,將節點用0補全。
if(t > k){
while(t > k){
t -= (k -1);
}
for(i=0; i<k-t; i++){
h.push(0);
}
}
//計算最小值,最小值是取最小值建立k元的哈夫曼樹,並求值。(權值*路徑長度)
long long MIN, sum;
MIN = 0;
while(h.num >= k){
sum = 0;
for(i=0; i<k; i++){
sum += h.top();
h.pop();
}
MIN += sum;
h.push(sum);
}
//計算最大值,最大值是取最大值建立2元哈夫曼常數,並求值。
long long MAX;
MAX = 0;
while(h2.num >= 2){
sum = 0;
for(i=0; i<2; i++){
sum += h2.top();
h2.pop();
}
MAX += sum;
h2.push(sum);
}
cout<<MAX<<" "<<MIN<<endl;
return 0;
}