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


    昨天参加了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]推得每个仓库执行的运输操作。

  • 相关阅读:
    Mysql字符串截取函数
    java 多线程系列---JUC原子类(一)之框架
    java 多线程系列基础篇(十一)之生产消费者问题
    java 多线程系列基础篇(十)之线程优先级和守护线程
    java 多线程系列基础篇(九)之interrupt()和线程终止方式
    java 多线程系列基础篇(八)之join()、start()、run()方法
    java 多线程系列基础篇(七)之线程休眠
    java 多线程系列基础篇(六)之线程让步
    java 多线程系列基础篇(五)之线程等待与唤醒
    java 多线程系列基础篇(四)之 synchronized关键字
  • 原文地址:https://www.cnblogs.com/lyfruit/p/3063766.html
Copyright © 2020-2023  润新知