1.题意:给你n个序列的长度和你现有的总资金 , 让你把这n个序列合并为一个,告诉你合并k个序列的花费是这k个序列的长度和,总花费不能超过你现有的总资金,问你最小的k是多少?(很显然一次性合并n个花费是最小的)
2.分析:很显然,二分k然后验证就可以了 。 但是验证的时候,还记得哈夫曼树吗?他是每次合并两个。
这里每次合并k个 , 也就是每次节点会减少(k - 1)个 , 我们最后要剩下一个,也就是总共要减少(n - 1)节点。
(1)如果(n-1)%(k-1)==0 : 也就是我们每次(从小到大)合并k个可以正好剩下一个,不用多余的合并了,这是应该是最小花费。
(2)如果(n-1)%(k-1)==m(m!=0):也就是我们每次合并k个的话,最后会剩下m个多余的,与其最后剩下m个最大的节点去和最后一个合并,不如刚开始合并 (m+1)个最小的把他们转化为k叉正哈夫曼树 , 这是最小花费。
注:这里直接在优先队列里面循环k合并k个的时候 , 会超时,所以我们这里把合并元素存进优先队列,初始元素循环判断num[i]和优先队列的top那个大 , num[i]大的话取i , i++; top大的话 , 取top , que.pop();
3.代码:
#include <iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 100000 + 10;
int num[maxn],n,cost;
bool judge(int k){
int sum = 0;
priority_queue<int,vector<int>,greater<int> > que;
int ans = 0;//记录合并元素的值
int grad;//记录应该合并几次
if((n-1)%(k-1)==0)grad = k;//整除则grad = k
else grad = (n-1)%(k-1)+1;//否则先合并 m+1个最小的
int i = 0;
int time = 0;//记录当前合并次数
while(i<n||!que.empty()){
if(sum>cost)return false;
if(time>=grad){//达到次数
grad = k;//以后合并k个
time = 0;//次数归零
if(ans!=0)
que.push(ans);
ans = 0;
continue;
}
if((que.empty()||num[i]<que.top())&&i<n){//num[i]<que.top
ans+=num[i];
sum+=num[i];
i++;
time++;
}
else if(!que.empty()){
int p = que.top();
que.pop();
ans+=p;
sum+=p;
time++;
}
}
if(sum>cost)return false;
return true;
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&cost);
for(int i = 0;i<n;i++){
scanf("%d",&num[i]);
}
sort(num,num+n);
int i = 2,j = n;
int ans = n;
while(i<=j){//二分
int mid = i + (j - i)/2;
if(judge(mid)){ans = mid;j = mid - 1;}
else i = mid + 1;
}
printf("%d\n",ans);
}
return 0;
}