题意:
解法:
显然二分.
二分答案mid,check其实就是计算mid叉哈夫曼树的权值.
可以用优先队列做,每次合并减少mid-1和节点,合并完只剩下一个节点.
但是如果(n-1)%(mid-1)!=0,
那么说明该哈夫曼树的下面存在一个节点,它的子节点不满k叉,
设rem=(n-1)%(mid-1),说明多出了rem的节点,
我们要优先消掉这rem个节点,因此一开始需要先合并rem+1个节点,
之后哈夫曼树就是完全二叉树了,直接模拟即可.
算法复杂度O(n*log*log).
提交代码之后发现会超时,需要优化.
考虑到合并的过程中,合并出的节点的权值是单调递增的,
因此我们可以开两个队列,一个队列q用来存为被合并过的节点,
另一个队列qq用来存合并出来的节点,
我们预先对a[]排序,那么队列q一开始是有序的,
同时由于合并出的节点权值是递增的,合并出的节点都丢进队列qq,
那么去队列qq也是有序的.
每次取数,我们只需要比较两个队列头部的数,取较小的即可.
这样check部分就是O(n)的了,
算法总复杂度O(n*log),可以通过此题.
code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxm=1e6+5;
int a[maxm];
int n,t;
bool check(int mid){
queue<int>q,qq;
for(int i=1;i<=n;i++){
q.push(a[i]);
}
int ans=0;
int rem=(n-1)%(mid-1);
if(rem){
int sum=0;
for(int i=1;i<=rem+1;i++){
sum+=q.front();q.pop();
}
ans+=sum;
if(ans>t)return 0;
qq.push(sum);
}
while(q.size()+qq.size()>1){
int sum=0;
for(int i=1;i<=mid&&(q.size()||qq.size());i++){
if(q.size()&&qq.size()){
if(q.front()<=qq.front()){
sum+=q.front();q.pop();
}else{
sum+=qq.front();qq.pop();
}
}else if(q.size()){
sum+=q.front();q.pop();
}else{
sum+=qq.front();qq.pop();
}
}
ans+=sum;
if(ans>t)return 0;
qq.push(sum);
}
return 1;
}
void solve(){
cin>>n>>t;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
int l=2,r=n;
int ans=n;
while(l<=r){
int mid=(l+r)/2;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
int T;cin>>T;
while(T--){
solve();
}
return 0;
}