傳送帶裝載貨物的最優解問題

題目:

傳送帶上依次送來了重量分別爲: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:本篇博文中,好多地方說了不少廢話,只是想盡量我的笨嘴表達的更清楚點,如果想交流的直接評論或者私信我就行~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章