洛谷 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 intmaxn = 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;
}