笔试题:环上货物均摊/糖果传递 解题报告(转载)
昨天参加了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与Gi之间应该满足如下条件:
0 = Pn + R1 - P1
0 = Pi-1+Ri - Pi (i≠1)
我们发现,通过不断递推,可以将Pi(i≠1)用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 = 0且Ti = 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]推得每个仓库执行的运输操作。