筆試題:環上貨物均攤/糖果傳遞 解題報告

昨天參加了2013年阿里巴巴實習生校園招聘的筆試。其中有一道題似曾相識,在快交卷的時候才隱約回想起這是一個數學問題。但具體怎麼做的卻想不起來了。爲了避免再次遺忘,所以還是動手自己再寫一寫吧。

題目參考:http://blog.csdn.net/hnmjiayou/article/details/8887127

解法參考:http://blog.sina.com.cn/s/blog_75683c7f0100q4va.html

代碼參考:http://50vip.com/blog.php?i=223

有一個淘寶賣家,他在全國有n個倉庫,這n個倉庫正好構成一個環形,如下圖一所示,開始他所有倉庫的貨物數是不等的,現在他想讓所有倉庫的貨物數都相等,如何運輸使總的運輸成本最低(成本=運貨量*路程),其中一次運輸只能在兩個相鄰的倉庫之間發生。試設計算法。


分析:

首先,題目規定運輸只能在兩個相鄰的倉庫之間發生,但並沒有規定相鄰的兩個倉庫什麼時候運輸,運輸的方向如何,以及運輸的次數。

但事實上,由於題目只要求使總的運輸成本最低,所以我們就我們只需要關心相鄰的兩個倉庫之間誰向誰運輸(即運輸的方向),以及相鄰兩個倉庫之間總的運貨量。而不必去關心這些運貨量是經過幾次運輸得來的。

考慮到要使總的運輸成本最低,那麼貨物是不應該在相鄰兩個倉庫之間來回折騰的。也就是說,相鄰兩個點之間的運輸的方向是確定的、唯一的。於是,我們可以把圖一中相鄰的一條邊看成是有向邊,並定義該有向邊的權值爲在改邊上要進行的總的運輸的貨物量。

我們還可對這個問題的描述做進一步的簡化抽象。我們可以規定,如果相鄰兩個邊的運輸是順時針進行的,那麼這次運輸的權值就是正的;如果運輸是逆時針進行的,則運輸的權值是負的。權值的絕對值表示相鄰兩個點之間運輸的貨物的總量。記每條邊的權值爲Pi。如圖二所示。


好了,到這裏,我們已經將問題簡化爲求出一個P1, P2,.....Pn的組合,在使運輸後每個節點相等前提下,最小。其中Pi在區間[-total, total]內取值total表示n個節點總的貨物量。問題轉化爲了一個枚舉問題,但事實上這條路並不可行,因爲要枚舉的空間太龐大了。

接下來我們繼續挖掘題目包含的信息。我們用Gi表示每一個倉庫的庫存量。用average表示平均的貨物量。並令Ri=Gi-average,表示第i個倉庫庫存量與平均庫存的差值。那麼Pi與Ri之間應該滿足如下條件:

0 = Pn + R1 - P1

0 = Pi-1+Ri - Pi i1

我們發現,通過不斷遞推,可以將Pii1)用P1的線性變換表示。令P1 = x。則有:

P2 = x + R2

P3 = x + R2 + R3

P4 = x + R2 + R3 + R4;

P5 = x + R2 + R3 + R4 + R5;

....

我們再次引入新的記號。令:

Pi = x - Ti。其中T1 = 0Ti = Ti-1 - Ri。

現在,求出一個使最小的Pi組合問題已經轉化爲求出使最小的x的值的問題。其中Ti是常數。我們發現,在將n個變量縮減爲一個變量之後,搜索空間已經大大減少。只需要在[-total, total]區間對x進行搜索即可(其中total表示n個節點總的貨物量)。到這一步,我們已經大大的縮減了搜索空間,但這個問題還可以做進一步優化。

我們將{Ti}按值散放在數軸上。通過觀察分析可知,當x等於{Ti}的中位數時,最小。

在求解出x的確定值之後。再利用Pi = x - Ti即可得推得每條邊的權值。而從上面的討論中可知。當Pi大於零時,表示倉庫i要向倉庫i+1運輸Pi個貨物(若i爲n,則表示i倉庫向1倉庫運貨)。當Pi小於零時,表示倉庫i向倉庫i-1運輸|Pi|個貨物(若i爲1,表示倉庫i向倉庫n運貨)。爲零,則不進行操作。

參考代碼:

#include <cstring>
#include <iostream>
#include <algorithm>
        
using namespace std;
const int X = 1000005;
typedef long long ll;
ll sum[X],a[X];
ll n;
ll Abs(ll x){
    return max(x,-x);
}
int main(){
    //freopen("sum.in","r",stdin);
    while(cin>>n){
        ll x;
        ll tot = 0;
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            tot += a[i];
        }
        ll ave = tot/n;
        for(int i=1;i<n;i++)
            sum[i] = a[i]+sum[i-1]-ave;
        sort(sum+1,sum+n);
        ll mid = sum[n/2];
        ll ans = Abs(mid);
        for(int i=1;i<n;i++)
            ans += Abs(sum[i]-mid);
        cout<<ans<<endl;//此處ans的值是總的運輸代價。
    }
    return 0;
}
上述代碼中輸出的ans是總的運輸代價。要獲取具體的運輸方案,需要另開闢一個存儲空間存儲排序前的sum值。獲取mid值之後再通過sum[i]推得每個倉庫執行的運輸操作。


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