传送带装载货物的最优解问题

本文通过二分查找算法解决卡车装载货物的问题,确保货物总重量不超过卡车的最大承载量。详细介绍了算法的设计思路和实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

传送带上依次送来了重量分别为:Wi(i=0,1,2,3.......,n-1)的n个货物。现在要将这些货物装到k辆卡车上。每辆卡车可装载的货物数大于等于0,但货物的重量总和不得超过卡车的最大运载量P.所有卡车的最大最大运载量一致。

请编写一个程序,输入n,k,Wi,求出装载全部货物所需的最大运载量P的最小值。

输入:第一行输入n和整数k,用空格隔开。接下来n行输入n个整数Wi,每个数占一行。

输出:输出P的最小值,占一行。

限制:1<=n<=10000

         1<=k<=10000

         1<=Wi<=10000


输入示例:                                                                       输出示例:

5 3                                                                                   10

8

1

7

3

9

示例解释:第一辆卡车装载两个货物:{8,1},第二辆卡车装两个货物:{7,3},第三辆卡车装一个货物{9},因此最大运载量的最小值为10.



分析:

首先想要解答这道题,最关键的点是题目中的首句:"传送带依次送来......",这句话是非常关键的,它告诉我们货物是按照传送传送货物的顺序依次将货物装载到卡车上,并不是货物的任意组合装载到卡车上。

其实这样想的话,题目反而简单了。我们就想象一个场景,多辆卡车在传送带钱等着装载货物,货物不断被装载到卡车上,直到卡车不能再继续装载下一个传送带送来的货物,也就是说即将达到卡车的最大装载量P,这时的话,该量卡车就退出装载,让下一辆卡车继续装载。

这样的话,我们就可以有着一下的思路:

我们首先假定每辆卡车的最大装载量P已知,在这种情况下,我们按照上述情形进行装载货物,我们最后记录下这k辆车最多能装载的货物数量N,如果说N=n,说明传送带送来的n个货物能被装载,也就是是说此时的装载量P是满足条件的;而当N<n时,自然就是n个货物没有完全装完,这时的最大装载量P肯定是不符合的。我们可以定义一个函数,以最大装载量P为参数,并返回能装载的最大货物数量N.

有了上述的能间接判断当前的最大装载量P是否符合要求的函数,这样的话,我们只需要将P从0不断自增,这样找打的第一个满足条件的P就是我们最终的答案。但是这样的话,光查找P的复杂度就有O(P),我们可以考虑使用之间介绍过的查找复杂度更低的二分查找---二分查找,这样便有了以下的完整源代码:

#include<iostream>
#include<cstdlib>
#define Max 10000
using namespace std;
int n,k;
                            
long long T[Max];
int check(long long P)
{
	int i=0;
	for(int j=0;j<k;j++)
	{
		long long s=0;
		while(s+T[i]<=P)              //在未超载的情况下,不断装载货物 
		{
			s+=T[i];
			i++;
			if(i==n) return n;   //已经装满了n个货物 
		}
	}
	return i;             //在货物未装完的情况下,直接返回当前已经装载的货物数量i 
	
}
int solve()
{
	long long left=0;
	long long right=Max*Max;    
	long long mid;
	while(right>left+1)
	{
		mid=(right+left)/2;
		int v=check(mid);         //利用check函数判断在最大装载量为P的情况下最多能装载的货物数量 
		if(v>=n)  right=mid;
		else left=mid;
		   
	}                                 
	return right;                      
}

int main()
{
	cin>>n>>k;
	for(int i=0;i<n;i++)
	{
		cin>>T[i];
	}
	long long ans=solve();
	cout<<ans<<endl;
	return 0;
}

本题在搞懂了第一句后,不算是一个难题。但是至于给出的源代码我想讲以下两点:

1.对于check函数,其时间复杂度其实是O(n),并不能根据循环的层数来推断出其复杂度为O(n^2),这是容易产生误解的。其实我们想象一开始我们对于时间复杂度的概念是什么?简单来说,它是一段程序中执行次数最多的语句与平均每条语句执行时间的乘积的一种渐进意义下的趋势,通常我们就用执行最多的语句次数作为时间复杂度。那么我们看看check中执行最多的基本语句是什么,应该是最内层while循环的语句,我们就以i++作为标准。大家可以想象之前建立的那个装货场景,对于n个货物,不断装载,装满则装入下一辆卡车,那么装载的最终停止是由什么决定的呢?是n个货物装载完或者n辆卡车被装满,其分别对应check程序中的return n和return i,我们再进一步想象,装载的停止动作其实和卡车的数量是没有太大关系的,假设我们是装载员,我们不断将货物装到卡车上,我们不管装的卡车是哪一辆,只要在没装满的情况下不断装货物就行.这种情况下,我们就可以得出check的复杂度是O(n).(其实这只能自己意会不可言传,我的嘴也比较笨,只能把我能想到的尽量用最明了的语句表达出来。如果大家实在理解不了,这个注意点也可以跳过去,不是太重要)

2.对于solve函数,之前我接触到的二分查找,都是查找指定的元素在数组中的位置,找不到就返回一个标志,找到则返回元素下标,但是这里我们返回的是满足大于等于n的第一个元素,关键在于solve没有并不是返回元素值等于查找值时的元素下标,从判断条件中if(v>=n) right=mid,可以看出right与mid始终是保持一种大于等于的关系的,大家可能会说这里哪来的数组,其实这里数组元素就是0~10000*10000,因为下标和元素值一样,就直接用下标代表元素值了。不懂的话大家还可以看一以下的代码:

#include<iostream>
#include<cstdlib>
#define Max 10
using namespace std;
int n;
                            //这种二分查找,并不是返回找到的元素,而是根据判断条件返回指定范围内的最后一个或者第一个元素 
int T[Max];
int solve()
{
	int left=0;
	int right=Max;
	long long mid;
	while(right>left+1)
	{
		mid=(right+left)/2;
		cout<<"T["<<left<<"]="<<T[left]<<" "<<"T["<<mid<<"]="<<T[mid]<<" "<<"T["<<right<<"]="<<T[right]<<endl;
		if(T[mid]>=n)  right=mid;
		else left=mid;
		   
	}                                  //前提:数组已经从小到大排序 
	return right;           
}          

int main()
{
	for(int i=0;i<Max;i++)
	{
		T[i]=i*3;
	}
	cin>>n;
	int index=solve();
	cout<<index<<" "<<T[index]<<endl;
	return 0;
}

上面的代码就是在0,3,6,9,12,15,18,21,24,27中进行二分查找,返回第一个大于等于输入的n的元素,不同于之前的精确查找元素的二分查找,这种二分查找的特点就在于其判断条件是T[mid]>=n,然后不断进行循环缩小right和left构成的范围,最终进行返回,其实针对这种二分查找,我也针对上述代码稍微进行了走总结:

                                       1.1:在 T[mid]>=n情况下,return right  返回第一个大于等于n的元素的下标
                                       1.2:在 T[mid]>=n情况下,return left  返回最后一个小于n的元素的下标
                                       2.1:在 T[mid]>n情况下,return right  返回第一个大于n的元素的下标
                                       2.2:在 T[mid]>n情况下,return left  返回最后一个小于等于n的元素的下标

                                       3.所有获得对应的下标如果超出下标范围,那么说明没有找到 ,比如在这里 T[mid]>=nreturn right,当n=30时,这里会返回10,而数组的下标范围是0~9,超出范围,说明找不到第一个大于n=30的值,因为最大的值是27

至于具体的循环过程中各种下标的变化情况,大家可以直接复制我上述的源代码进行查看~

(当然,这里不会出现T[mid]<=n或者T[mid]<n的情况,因为这里数组0,3,6,9.......是按照升序排列的,肯定要满足二分查找的最基本要求,要保证n在不断缩小的另一半范围中。)


PS:本篇博文中,好多地方说了不少废话,只是想尽量我的笨嘴表达的更清楚点,如果想交流的直接评论或者私信我就行~

实验1 Java语言基础 4 一、实验目的 4 二、实验要求 4 三、实验内容 4 (一) 声明不同数据类型的变量 4 (二) 了解变量的使用范围 5 (三) 使用关系运算符和逻辑运算符 5 (四) 使用表达式语句与复合语句 6 (五) 使用选择语句 6 (六) 使用循环语句 8 实验2 面向对象编程 11 一、实验目的 11 二、实验要求 11 三、实验内容 11 (一)创建 Applet 应用小程序 11 (二)创建对象并使用对象 12 (三)编写显示当前日期和时间的程序 13 (四)使用修饰符 14 (五)方法中参数传递 15 (六)类的继承性 16 (七)类的多态性 18 实验3 包、接口与异常处理 22 一、实验目的 22 二、实验要求 22 三、实验内容 22 (一)了解并使用 Java 的系统包 22 (二)创建并使用自定义包 22 (三)使用接口技术 24 (四)了解异常处理机制 25 实验4 常用系统类的使用 27 一、实验目的 27 二、实验要求 27 三、实验内容 27 (一)了解 Applet 的生命周期 27 (二)使用数学函数类 28 (三)使用日期类 29 实验5 建立图形用户界面 32 一、实验目的 32 二、实验要求 32 三、实验内容 32 (一)创建图形用户界面 32 (二)了解事件处理机制 34 (三)建立独立运行的窗口界面并使用匿名类 36 (四)使用 Swing 组件 39 (五)使用自定义对话框与内部类 41 实验6 图形处理 44 一、实验目的 44 二、实验要求 44 三、实验内容 44 (一)使用图形类 Graphics 44 实验7图形处理(二) 47 一、实验目的 47 二、实验要求 47 三、实验内容 47 (一)幻灯机效果——连续显示多幅图像 47 (二)使用滚动条改变背景颜色 48 (三)Applet 与 Application 合并运行 49 (四)创建电闪雷鸣的动画 50 实验8 流与文件 53 一、实验目的 53 二、实验要求 53 三、实验内容 53 (一)使用标准数据流的应用程序 53 (二)使用文件输入输出流的应用程序 54 (三)使用随机文件类的应用程序 54 (四)使用数据输入输出流与文件输入输出流类的应用程序 55 (五)使用对象输入输出流的应用程序 56 实验9 线程 58 一、实验目的 58 二、实验要求 58 三、实验内容 58 (一)Thread子类的方法实现多线程 58 (二)实现Runnable接口的方法实现多线程 59 实验10 数据库的连接:JDBC 61 一、实验目的 61 二、实验要求 61 三、实验内容 61 (一)配置ODBC数据源 61 (二)编写程序,按照下表的结构建立"student"表 61 (三)编写程序,完成填写功能 62 (四)编写程序,完成查询功能 62
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值