1. 程式人生 > >[貪心] 合併果子

[貪心] 合併果子

BZOJ  3669  合併果子



題目描述

在一個果園裡,多多已經將所有的果子打了下來,而且按果子的不同種類分成了不同的堆。多多決定把所有的果子合成一堆。

每一次合併,多多可以把兩堆果子合併到一起,消耗的體力等於兩堆果子的重量之和。可以看出,所有的果子經過n-1次合併之後,就只剩下一堆了。多多在合併果子時總共消耗的體力等於每次合併所耗體力之和。 

因為還要花大力氣把這些果子搬回家,所以多多在合併果子時要儘可能地節省體力。假定每個果子重量都為1,並且已知果子的種類數和每種果子的數目,你的任務是設計出合併的次序方案,使多多耗費的體力最少,並輸出這個最小的體力耗費值。

例如有3種果子,數目依次為1,2,9。可以先將1、2堆合併,新堆數目為3,耗費體力為3。接著,將新堆與原先的第三堆合併,又得到新的堆,數目為12,耗費體力為12。所以多多總共耗費體力=3+12=15。可以證明15為最小的體力耗費值。 

輸入

輸入包括兩行,第一行是一個整數n,表示果子的種類數。第二行包含n個整數,用空格分隔,第i個整數ai是第i種果子的數目。

1<=n<=10000,1<=ai<=20000

輸出

輸出包括一行,這一行只包含一個整數,也就是最小的體力耗費值。輸入資料保證這個值小於2^31。 

樣例輸入

3

1 2 9

樣例輸出

15


解題思路

首先看到這個題很容易想出一種貪心思路:

每一次合併之前,都將現在的沒堆果子從小到大排序,然後再合併最小的兩堆

其實這就是正解,是不是很簡單

由於資料範圍過大,嘗試一下就知道按照這種方法做肯定會超時,那麼我們就需要想更優的方法。但其實我們的貪心思想是正確的,而這道題難就難在此處

我們可以想一下有什麼資料結構可以自動排序,沒錯,聰明的你肯定會想到優先佇列

引入優先佇列之後,這道題就簡單多了,因為優先佇列的處理時間是很短的,所以根本不用考慮超時這一問題

所以這道題用優先佇列解的思路就是:

先將所有果子入隊,然後每次再取出前兩個元素,將它們的和入隊,最後剩下的一個元素就是答案

參考程式碼

#include<cstdio>
#include<queue>
#include<cstring>
#define reg register
using namespace std;
priority_queue<int, vector<int>, greater<int> > p;  //定義小根堆
int n, x, y, ans;
inline void read(int &x){   //輸入優化
    x = 0; int f = 1; char s = getchar();
    while(s < '0' || s > '9') {if(s == '-') f = -1; s = getchar();}
    while(s >= '0' && s <= '9') {x = x * 10 + s - 48; s = getchar();}
    x *= f;
}
inline void write(int x){   //輸出優化
    if(x < 0) {putchar('-'); x = -x;}
    if(x / 10) write(x / 10);
    putchar(x % 10 + 48);
}
int main(){
    read(n);
    for(reg int i = 1;i <= n;i ++){ //初始入隊
        read(x);
        p.push(x);
    }
    for(reg int i = 1;i <= n - 1;i ++){ //求解
        x = p.top();    //取出最小的兩個元素
        p.pop();
        y = p.top();
        p.pop();
        p.push(x + y);  //將它們的和入隊
        ans = ans + x + y;
    }
    write(ans);
    putchar('\n');
    return 0;
}