题目链接
题目给n个重量w[i],现在要用一个承重为w的卡车来装货物,然后要求不超过r次装完,要求w的最小值;
题目中给定了装货物的方案,是使用贪心的方法,即,每一次选择装上车的货物,都是目前所剩的货物中,最重的而且能装上车的;
注意,装上车的货物不一定是剩下的货物中最重的,而是所剩的货物中,而且能装上车的货物中的最重的;
首先我们想,能不能用一辆承重为x的车,来运完货物,这个函数为check,用multiset来维护,代码如下:
inline bool judge(int x){
int sum=x,cnt=0;
A.clear();
for(int i=1;i<=n;i++) A.insert(a[i]);
while(!A.empty()){
while(!A.empty() && sum>=*A.begin()){
it=A.upper_bound(sum);
it--;
sum-=*it;
A.erase(it);
}
sum=x;
cnt++;
if(cnt>r) break;
}
return cnt<=r;
}
思路是这样的,我们首先把所有的货物都放进multiset中(注意,multiset是允许多个元素的set,而且在加入时就完成了从小到大排序),然后每次都找出能装上车(所剩货物中最小值能装上车)中的货物中的最大重量的货物(lower_bound的前一项)
完成judge函数后,进行二分找答案就可以了;
int left=ma,right=sum,mid;
while(left<right){
mid=(left+right)>>1;
if(judge(mid)) right=mid;
else left=mid+1;
}
然后我们发现这样的结果是WA的……
原因是,对于承重w来说,结果并不是单调的, 如果一辆承重为w的卡车能装下这些货物,并不意味着承重w+1的卡车也能装下这些货物,因为货物的分配可能与上一中方案不同,偏离了最优方案;
我们可以想到,对于递增的w而言,我们的judge函数的值应该是类似于这样的:
w | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
judge(w) | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
从1到4都是不可行的一段0,到5开始,是可行的w的最小值,就是要求的答案,在5之后,不一定全是1,但是在10之后,就全是1了;
但是我们可以发现,对于judge(w)如果为true,那么对于w+max(w
[i])也一定是满足条件的, 因为对于每一轮,我们都可以甚至多装一个货物(因为比起载重为w而言,多出的空间比最大的那个货物的重量还要大),所以,其实我们二分找到的那个答案ans,不一定是最小的w,只是一个次优解,那么最优解一定不会比ans小max(w[i])以上,所以我们可以暴力去从ans到ans-max(w[i])检验,是否这个载重值可以装下所有货物;找到的能装下所有货物的那个最小的w就是答案;
最终ac代码如下:
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxx=2006;
int n,r,ma=0;
int a[maxx];
multiset<int> A;
multiset<int>::iterator it;
inline bool judge(int x){
int sum=x,cnt=0;
A.clear();
for(int i=1;i<=n;i++) A.insert(a[i]);
while(!A.empty()){
while(!A.empty() && sum>=*A.begin()){
it=A.upper_bound(sum);
it--;
sum-=*it;
A.erase(it);
}
sum=x;
cnt++;
if(cnt>r) break;
}
return cnt<=r;
}
int main() {
std::ios::sync_with_stdio(false);
cin>>n>>r;
int sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
ma=max(ma, a[i]);
}
int left=ma,right=sum,mid;
while(left<right){
mid=(left+right)>>1;
if(judge(mid)) right=mid;
else left=mid+1;
}
for(int i=left-ma;i<=left;i++)
if(judge(i)) return 0 & printf("%d\n",i);
}
实际上我们发现,测试数据并不变态,在最后检验的时候,即使我们少检验一些区间,比如50个,就是区间为 ans到ans-50,也能找到正确答案,如下:
for(int i=left-50;i<=left;i++)
if(judge(i)) return 0 & printf("%d\n",i);