【JZOJ5677】纽约

题目背景

印度洋暖流温润着纽约,四季丰沛的雨水造就了一望无际的大草原。蒙古包是纽约最独特的一道风景线,每至二月中旬,纽约的土著傣族人民又开始半年一度的转场了。

题目描述

由于牲畜和行李过多,牧民 Azone 不得不多次往返于两个草场之间运输家当。为了顺利转场,Azone 决定花费 ww元津巴布韦币,购买一辆载重为 ww 的汽车。共有 nn 件家具需要搬运,每件家具的重量为 w_iwi​ 。Azone 每次出发前,会搬若干件总重不超过 ww 的物品上车:出发前,车是空载的,Azone 会选择能搬上车的家具中最重的一件放上车(即该家具之前还未运走且放置该家具后汽车不会超载),然后在剩下的家具中继续选择一件能被搬走的最重的上车,持续装车,直至剩下的家具都塞不上车。装载完毕后,Azone 会开车运走这些家具,卸在目的地,再驾空车返回继续运送,直至转场完毕。

Azone 希望在运送次数不超过 RR 的情况下完成转场,求 Azone 最少需要购置价值多少的车。

输入输出格式

输入格式:

第一行,两个整数 nn 和 RR ,分别表示家具件数及最多运送次数。

第二行,若干个整数 w_iwi​ ,表示家具重量。

输出格式:

一行,表示答案。

输入输出样例

输入样例#1:

6 2
26 7 10 30 5 4

输出样例#1:

42

说明

对于 100% 的数据, 1 ≤ R,n,w_i ≤ 20001≤R,n,wi​≤2000 。

--------------------------------------------------------------------------------------------------------------------------------

再明显不过的二分,可打完二分,提交一看,wa了不少的点。好吧,其实这个二分还是有点难度的。

二分思路即我们每次二分一个w,判断是否合法,即是否可以在不超过r轮运完,如果可以那就尝试更小的,如果不可那就更大点。可这样其实错误的,因为这个w并不一定是严格单调的,即对于w可以,w+1却未必可以。有点难理解,但事实却是这样,可以这样认为:虽然我的空间大了,但是我的策略却还是一成不变的(每次只从最重装起),而我们的这个策略显然不是最优的,即我变大的空间不不一定能够弥补我并不是最优的策略。

那么该怎么做呢?

细想一下,这道题是二分绝对没错,那也就是说一定存在单调的量——对于w可以,那么w+max(wi)也一定可以,其中max(wi)表示最重的家具的重量。

接下来我们来证明它的正确性:

如果w可以,那也就是说,我们每次装的不会超过w,而现在如果w变为了w+max(wi),如果我们每轮装得小于w的话,分两种情况,一种是只剩下这么重的家具了,那么很显然就是装完了,成立;另一种就是装不了了,可这是不可能的,因为当前装了小于w的家具(设为ws),如果装不了也就是说要有一件家具使得ws+wi>w+max(wi),显然不可能,因为ws<w且wi<=max(wi)。换句话说就是我们每轮装得都是>=w的(除了上面讨论的第一种情况,但那也能装完),那么r轮肯定装得完,那么上面的式子就得到了证明。

上面的证明可能没看明白,其实也可自己想一下,还是比较好理解的。

总结:二分除了注意边界还要找一下单调的量,即二分的对象。

code:

#include<iostream>
#include<set>
using namespace std;

const int N = 2e3+10;
multiset<int> s;
int n, r, ans, a[N];

bool check(int ans) {
    int i, temp;
    s.clear();
    for (i = 1; i <= n; i++)
        s.insert(a[i]);
    for (i = 1; i <= r; i++) {
        temp = ans;
        while (!s.empty()) {
            set<int> ::iterator it = s.upper_bound(temp);
            if (it == s.begin()) break;
            it--;
            temp -= *it;
            s.erase(it);
        }
        if (s.empty()) return true;
    }
    return false;
}

int main() {
    cin >> n >> r;
    int i, x; 
    for (i = 1; i <= n; i++)
        cin >> a[i];
    int l = 0, r = 1e6;
    while (r-l > 1) {
        int mid = (l+r)>>1;
        if (check(mid)) r = mid;
        else l = mid;
    }
    ans = r;
    for (i = r; i >= r-2000 && i>0; i--) {
        if (check(i)) ans = i;
    }
    cout << ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值