笔试题:环上货物均摊/糖果传递 解题报告(转载)

昨天参加了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个仓库库存量与平均库存的差值。那么PiGi之间应该满足如下条件:

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;

....

我们再次引入新的记号。令:

P1 = 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]推得每个仓库执行的运输操作。

原文地址:https://www.cnblogs.com/lyfruit/p/3063766.html