题目:http://acm.hdu.edu.cn/showproblem.php?pid=5884
题意:
给出一个数列,每次可以选k个数合并,代价是这k个数的和,最后合并成一个数以后要求总代价不大于m,问最小的k是多少?
分析:
二分答案,然后判断k是否可以。
怎么取才能最优呢?显然是大的数越往后取越优,因为这样才能加的次数少啊!
因为每次都会减少k-1个,一共减少了n-1个,所以如果(n-1)%(k-1)==0,需要(n-1)/(k-1)次合并,否则,多需要一次合并,那么这次合并可以填几个0,或者第一次先把多的几个数给合并了都可以。
维护的时候用单调栈。
后来题解说是哈夫曼树,QAQ,还真是!跟哈夫曼树的构造一样啊!
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100000+9;
int a[N],n;
ll m;
bool ok(int k) {
queue<ll>q1,q2;
int cnt=0;
ll sum=0,tot=0;
int x=(n-1)%(k-1);
if(x)
for(int i=0; i<k-1-x; i++)q1.push(0);
for(int i=0; i<n; i++)q1.push(a[i]);
while(!q1.empty()||q2.size()>1) {
cnt=0,sum=0;
while(cnt<k&&(!q1.empty())&&(!q2.empty())) {
if(q1.front()<q2.front())sum+=q1.front(),q1.pop();
else sum+=q2.front(),q2.pop();
cnt++;
}
while(cnt<k&&q1.empty()&&!q2.empty())sum+=q2.front(),q2.pop(),cnt++;
while(cnt<k&&q2.empty()&&!q1.empty())sum+=q1.front(),q1.pop(),cnt++;
q2.push(sum);
tot+=sum;
if(tot>m)return 0;
}
return tot<=m;
}
int main() {
//freopen("f.txt","r",stdin);
int T;
scanf("%d",&T);
while(T--) {
scanf("%d%lld",&n,&m);
for(int i=0; i<n; i++)scanf("%d",&a[i]);
sort(a,a+n);
int l=2,r=n;
while(l<r) {
int mid=l+(r-l)/2;
if(ok(mid))r=mid;
else l=mid+1;
}
printf("%d\n",l);
}
return 0;
}