题目链接:https://vjudge.net/problem/HDU-5884
题意:有n个数,每个数有个值,现在你可以选择每次K个数合并,合并的消耗为这K个数的权值和,问在合并为只有1个数的时候,总消耗不超过T的情况下,最小的K是多少
题解:首先二分肯定容易想到,然后再就是优先队列合并操作一下,这样n*log(n)*log(n),就超时了,所以我们怎么能用一种O(n)的方法呢,想一下,第二次合并得到的数一定比第一次合并得到的数大,所以我们就把合并的数单独放在一起,新合并的直接放到后面,两个序列直接尺取就ok了。
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n;
ll m;
ll a[N];
queue<ll> q;
bool judge(ll x) {
// cout<<x<<":"<<endl;
while(!q.empty()) q.pop();
ll res = 0, cnt = 0;
ll len = n, num;
int i = 1;
if((n - 1) % (x - 1) != 0) {
cnt = (n - 1) % ( x - 1);
for(i = 1; i <= cnt + 1; i++)
res+=a[i];
q.push(res);
}
len = n - cnt;
while(len > 1) {
cnt = 0;
num = 0;
while(num < x && i <= n && !q.empty()) {
if(a[i] > q.front()) {
cnt += q.front();
q.pop();
} else {
cnt += a[i];
i++;
}
num++;
}
while(num < x && i <= n) {
cnt += a[i];
i++;
num++;
}
while(num < x && !q.empty()) {
cnt += q.front();
q.pop();
num++;
}
len -= num;
res += cnt;
q.push(cnt);
len++;
if(res > m) return 0;
}
return 1;
}
int main() {
int T;
ll l, r, mid;
ll ans;
scanf("%d", &T);
while(T--) {
scanf("%d %lld", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
sort(a + 1, a + 1 + n);
l = 2, r = n;
while(l <= r) {
mid = (r + l) >> 1;
if(judge(mid)) {
r = mid - 1;
ans = mid;
} else {
l = mid + 1;
}
}
printf("%lld\n", ans);
}
return 0;
}