【二分】洛谷U33405 纽约

题目链接
题目给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);

猜你喜欢

转载自blog.csdn.net/qq_33982232/article/details/81457779