1. 程式人生 > >洛谷 T63713 合併果子

洛谷 T63713 合併果子

題目:
小Z很喜歡在果林裡漫步,望著樹上的果子,忍不住開始摘果子了。

他把果林裡的n棵樹上的果子都摘下來了(這也太過分了吧)!他還無聊地輸出了每一棵果樹上的果子個數$num[i]$。

小Z又決定把所有的果子都合成一堆。每一次合併,小Z把兩堆果子合併到一起,消耗的體力為兩堆果子的重量之和。顯然,所有的果子經過$n-1$次合併後,就只剩下一堆了。小Z在合併果子時總共消耗的體力等於合併所耗體力之和。

由於小Z特別地懶,所以他想知道自己合併果子所消耗的體力最少為多少。

輸入:

第一行,輸入一個數n,表示果林中樹的棵數。

第二行,包括n個數,第$i$個數$num_i$表示第i棵果樹上的果子個數。

第三行,輸入一個數q,表示有q組詢問。

接下來q行,每次輸出兩個數l和r,表示將$[l,r]$區間內所有果樹合併成一堆所對應的答案。
輸出:

輸出共q行,每次輸出對應消耗的最少體力。

樣例輸入:
3
1 2 9
3
1 3
1 2
2 3

樣例輸出:
15
3
11

【資料範圍】

對於30%的資料,$1 <= n <= 1000, 1 <= q <= 100, num_i <= 1e3$

對於60%的資料,$1 <= n <= 100000, 1 <= q <= 10, num_i <= 1e4$

對於100%的資料,$1 <= n <= 100000, 1 <= q <= 500, num_i <= 1e5$.

#資料不保證l<=r!

Solution

$Subtask #1$
將l到r的所有果子都掃一遍,每次排個序。
複雜度:$O(q * n^2 * logn)$,得分:30分。

$Subtask #2$
考慮到每次排個序有很多冗餘的情況,且每次都是取最小的兩個,自然想到用堆來維護l到r區間的果子。
每次取兩次堆頂,並將合併後的數加入堆中即可。這些是堆的基本性質。
複雜度:$O(q*n*logn)$,得分:60分。

 

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int
maxn = 100005; ll heap[maxn], cnt; int a[maxn]; int n, q; void up(int u) { int fa = u >> 1; if (fa > 0) { if (heap[fa] > heap[u]) { swap(heap[fa], heap[u]); up(fa); } } } void down(int u) { int son = u << 1; if (son <= cnt) { if (son < cnt && heap[son + 1] < heap[son]) son++; if (heap[u] > heap[son]) { swap(heap[u], heap[son]); down(son); } } } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); scanf("%d", &q); while (q--) { int l, r; scanf("%d %d", &l, &r); if (l > r) swap(l, r); cnt = 0; for (int i = l; i <= r; i++) { heap[++cnt] = a[i]; up(cnt); } ll ans = 0; for (int i = l; i < r; i++) { ll ad = heap[1]; heap[1] = heap[cnt--]; down(1); ad += heap[1]; heap[1] = heap[cnt--]; down(1); ans += ad; heap[++cnt] = ad; up(cnt); } printf("%lld\n", ans); } return 0; }

 

 

 

$Subtask #3$
題目中給出了果子數<=1e5,所以從這裡突破,每次l到r區間的果子可以用桶排序來使得有序。
此時我們獲得了一個單調佇列(即單調遞增)。同時我們再維護一個合併後的單調佇列。
每次取兩個最小的數,即為兩次比較兩個單調佇列的隊頭大小,並每次取小的隊頭,並將合併後的數加到合併的單調佇列的隊尾即可。
複雜度:$O(qn)$,得分:100分。

程式碼:

#include <bits/stdc++.h>
using namespace std;

#define clean(s, k) memset(s, k, sizeof(s))
#define RE register
#define rep(i, l, r) for (RE int i = l; i <= r; i++)
typedef long long ll;
const int maxn = 100005;
int a[maxn], cnt[maxn];
ll b[maxn], s[maxn];
int hb, tb, hs, ts;
int n, q, l, r;

int read() {
    int num = 0;
    char op = getchar();
    while (!isdigit(op)) op = getchar();
    while (isdigit(op)) {
        num = 10 * num + op - '0';
        op = getchar();
    }
    return num;
}

void pre() {
    clean(cnt, 0), clean(s, 0x7f); clean(b, 0x7f);
    hs = hb = 1, ts = tb = 0;
    
    int mn = 100000, mx = 0;
    rep(i, l, r) {
        cnt[a[i]]++;
        mn = min(mn, a[i]);
        mx = max(mx, a[i]);
    }
    rep(i, mn, mx)
        rep(j, 1, cnt[i])
            b[++tb] = i;
}

ll calc() {
    ll ans = 0;
    rep(i, l, r - 1) {
        ll add = 0;
        if (b[hb] < s[hs]) add = b[hb++];
        else add = s[hs++];
        if (b[hb] < s[hs]) add += b[hb++];
        else add += s[hs++];
        ans += add;
        s[++ts] = add;
    }
    return ans;
}

int main() {
    n = read();
    rep(i, 1, n)
        a[i] = read();
    q = read();
    rep(QAQ, 1, q) {
        l = read(), r = read();
        if (l > r) swap(l, r);
        pre();
        
        printf("%lld\n", calc());
    }
    return 0;
}