港口的集装箱

题目

要把即将到达港口的集装箱放到船上,但集装箱只能按顺序到达港口,船也只能按顺序停靠港口,也就是说放完第一个集装箱才能放置下一个,第一艘船开走之后才能给第二艘船放集装箱。现在给出集装箱的数量n(1<n<100000)和船的数量s(1<s≤n),下面的n行每行一个数(输入顺序就是集装箱到达港口顺序),表示每个集装箱的重量。已知每艘船的载重都相等,船上集装箱的重量不能超过载重,求船的最小载重。
输入样例
7 5
10
40
30
10
50
11
40
输出样例
50
样例解释
第一艘船装(10,40),第二艘(30,10),第三艘(50),第四艘(11),第五艘(40),所以最小载重50

分析

采用二分法。按照题意我们进行顺序装箱,若在载重量为load下,s艘船能装载n个给定重量的集装箱,我们称其为满足题意的。易知载重量load越大,该性质就越满足,而load越小,就越难以满足该性质,因而我们求满足该性质的边界,也即最小的载重量load。
在check()中判别在load下,是否满足性质。这里我们判断装完集装箱后,所需船只数是否大于题中所给的数量s。
在主函数中,采用整数二分,二分的Left可任意取某个集装箱的重量,Right取所有集装箱的重量和。

代码

#include <iostream>
using namespace std;

const int N = 1e5 + 10;
int n, s; //集装箱和船
int load;
int q[N];//集装箱重量
int ship[N];

//判断装完集装箱后,所需船只数是否大于题中所给的数量s。
bool check(int load, int n, int s){
	int k = 0;//当前船数 
	for(int i = 0; i < N; i ++ ) ship[i] = 0;//船的载重状态 
	//装载货物
	for(int i = 0; i < n; i ++ ){
		if(q[i] > load) return false;
		else{
				if(ship[k] + q[i] <= load) ship[k] += q[i];
				else{
			 	  k += 1;
				  ship[k] = q[i]; 
				  }
			}
	}
	if(k >= s) return false;  //所需船只数是否大于船的数量s
	else
	 return true;
}

int main(){
	scanf("%d%d", &n, &s);
	for(int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
	
	int sum = 0;
	for(int i = 0; i < n; i ++ ) sum += q[i];
	
	int l = q[0], r = sum;
	while(l < r){
		int mid = l + r >> 1;
		if(check(mid, n, s)) r = mid;
		else l = mid + 1;
	}
	cout << l << endl;
//	cout << check(500, n, s) << endl;
//	cout << n << " " << s << endl;
//	for(int i = 0; i < n; i ++ ) printf("%d ", q[i]); 
	return 0;

}

另解:
其中check()函数从集装箱角度考虑问题,如上所示,判断装完集装箱后,所需船只数是否大于题中所给的数量s,该种写法好处在于,若某个货物q[i]就比载物量load大,则直接结束遍历,函数返回 false。
而参考评论区,也可以从船的角度来考虑,判断s艘运载量为load的船装下的货物数是否不小于n件,于是修改check()函数,写法如下所示:

//s艘运载量为load的船能装多少件货物
bool check(int load, int n, int s){
	int i = 0;//货物标号 
	//对s艘船 
    for(int j = 0; j < s; j ++ ) {
        int si = 0;
        while(si + q[i] <= load) {
            si += q[i];
            i ++;
            //在遍历s艘船的范围内即可装满n个货物,则满足性质
            if(i == n) return true; 
        }
    }
    return i >= n;//s艘船最多装i件货物,与n比较
}

总结

若对题目进行改动,求最大载重量load,使得s艘船无法满足能装完货物的性质,可以修改二分算法如下所示,此时 mid = ( l + r + 1 ) / 2 .

while(l < r){
		int mid = l + r + 1 >> 1;
		if(!check(mid, n, s)) l = mid;
		else r = mid - 1;
	}

求得算例答案为49,这自然是正确的,比原答案50少1。

总结如上分析,整数二分的重点在于写好check()函数,以及注意边界问题,在原题的二分法中,由于r = mid,因而取中点int mid = l + r >> 1;
而若 l = mid(改动需求后),则取中点int mid = l + r +1 >> 1,这是为了避免陷入死循环。
整数二分的两种常见写法如下所示:

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值