題目鏈接
題意:一個人很喜歡喫冰淇凌,他想要把N個冰淇凌球堆成高爲K的冰淇凌球塔,但是爲了穩定,下面的冰淇凌球的尺寸要大於等於它緊挨着的上面的冰淇凌球的兩倍。然後要求這N個冰淇凌球最多可以組成多少個高爲K的冰淇凌球塔。
假思路:首先會想到用貪心來堆冰淇凌球,先選擇一個最大的當作最底層的,然後開始枚舉或者二分查找下一個小於或等於它一般尺寸的冰淇凌球,一直找啊找下去,直到滿足K,然後再開始第二輪。這個看似很好的思路其實還是有BUG的。 請看下面這個例子:
K = 3
1 1 2 2 5 10
這個例子用上面的思路得到的答案就是 只能堆1個冰淇凌塔 就是 2 5 10。 但是其實正解有兩個分別是1 2 10 和 1 2 5。所以這個思路不行(從前往後找也一樣,或者從前往後找一遍,再從後往前找一遍取MAX 也不行, 看這個例子:
K = 3
1 1 2 4 8 9
正確思路: 二分找答案, 然後再用貪心來驗證。
二分: 這個題目的答案總會在(0,n/k)之間,每一次對算出的mid進行驗證,如果符合要求那麼區間左邊更新爲mid,如果不符合要求,區間右邊就更新爲mid-1。直到L >= R爲止。
驗證:可能的答案值數量 = n。 用一個新的數組存入前n個冰淇凌球的尺寸,每n個冰淇凌分爲一組當作一個間隔,通過對原數組的遍歷如果該位置滿足 b[pos] >= 2*a[i-n] 那麼就將a[i]的值更新爲原數組的值,這樣對應每組中的對應位置就是同一個冰淇凌球塔。如果中途原數組已經更新到最後一個元素還沒有堆滿n個冰淇凌球塔,那麼這個答案就是錯的,沒用完或者剛好用完就證明這個答案合法,可以更新。
代碼表述更加清晰一些:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int T, n, k;
ll a[300005], b[300005];
bool judge(int x) { // 判斷答案是否合法
for(int i = 1;i <= x; i++) {
a[i] = b[i];
}
int pos = x+1;
for(int i = x+1;i <= x*k; i++) {
while(pos <= n && a[i-x]*2 > b[pos]) pos++;
if(pos == n+1) return 0;
a[i] = b[pos]; pos++;
}
return 1;
}
int find(int l, int r) { // 通過二分查找答案
while(l < r) {
int mid = (l + r + 1) / 2;
if(judge(mid)) l = mid;
else r = mid-1;
}
return l;
}
int main() {
scanf("%d", &T);
for(int p = 1;p <= T; p++) {
scanf("%d %d", &n, &k);
for(int i = 1;i <= n; i++) {
scanf("%lld", &b[i]);
}
sort(b+1, b+1+n);
int ans = find(0, n/k);
printf("Case #%d: %d\n", p, ans);
}
}