[UVA11300]Spreading the Wealth

2020.7.30新更新,之前不会用(LaTeX),现在加上了……

博客园上整个重写了,丢了一条LC大佬的评论QAQ

题目

原题链接

解说

真是一道苏维埃气息极其浓郁的题目啊,共产主义马上就要实现了!(财富液化委员会表示很赞)

[UVA11300]Spreading the Wealth

但可惜的是这道题并没有那么友善。这是一道数学题。(兄弟们把害怕打在公屏上)

在经过繁杂的思考的思考后,我觉得思路大概就是下面这样:

首先非常显然最后所有人的金币都要变成(ave=frac{sum}{n}=frac{A_1+A_2+A_3+ dots + A_n}{n})(A_i)为原数组)。

由于金币只能在相邻的人之间传递,所以我们不妨设(X_i) 代表(i)(i-1)号传递的硬币(正负表示方向,正数表示(i)(i-1)传递的,负数表示(i-1)(i)传递的,自然,(0)代表不用传递。由于是一个环,(X_1)就表示(1)(n)号之间的关系)。

由于最后所有人金币均为(ave),所以每个人的原金币减给出去的加拿过来的结果必定是(ave),即(A_i-X_i+X_{i+1}=ave)。移个项,我们得到(X_{i+1}=ave-A_i+X_i)。由此,我们可以得到一列数组:

[X_2=ave-A_1+X_1 ]

[X_3=ave-A_2+X_2=2 imes ave-A2-A1+X1 ]

[X4=ave-A3+X3=3 imes ave-A3-A2-A1+X1 ]

[dots ]

[X_n=(n-1) imes ave-A_{n-1}-dots-A_2-A_1+X_1 ]

非常显然我们看到(X_i=(i-1) imes ave- A_{i-1} -dots-A_2-A_1+X_1),每个最后都有(X_1),但是前面不太一样,那么我们不妨把它简化一下。设(C_i=A_1+A_2+dots+A_{i-1} - (i-1) imes ave),那么我们可以化简上面这一坨式子,得到:

[C_1=0 ]

[X_2=X_1-C_2 ]

[X_3=X_1-C_3 ]

[X_4=X_1-C_4 ]

[dots ]

[X_n=X_1-C_n ]

这样的话就可以转回来看看我们要求什么了。答案应为(vert X_1vert + vert X_2 vert + vert X_3 vert+dots+vert Xn vert)的最小值,根据上面得到的(X_i =X_1-C_i),我们的答案可化为(vert X_1 vert+vert X_1-C_2vert+vert X_1-C_2 vert +dots+vert X_1-C_nvert)。现在我们只要找一找选哪个数当做(X_1)可以使上式最小就行了。

我们知道(vert X_1-C_ivert)在数轴上表示两点之间距离,因此此题最终转化为在数轴上求一个点(X_1),使其到点(0,C_2,C_3 dots C_n)的距离之和最小。显然,(X_1)为这些数的中位数的时候这个数是最小的 (怎么证明?回去看自己的初中课本谢谢)

那么,就是这样。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1000000+2;
typedef long long ll;//数据范围显示要开long long
ll a[maxn],sum,ave,c[maxn];
int n;
int main(){
    while(scanf("%d",&n)!=EOF){
        sum=0;
        for(int i=1;i<=n;i++) {
            scanf("%lld",&a[i]);
            sum+=a[i];
        }
        ave=sum/n;
        c[1]=a[1]-ave;
        for(int i=2;i<=n;i++) c[i]=c[i-1]+a[i]-ave;
                //上面这两行用于计算Ci
        sort(c+1,c+1+n);
        ll mid=c[n/2];//中位数
        ll ans=0;
        for(int i=1;i<=n;i++) ans+=abs(mid-c[i]);
        printf("%lld
",ans);
    }
    return 0;
}

尾声

其实上面的代码是有BUG的(SURPRISE!)

由于数据比较水,上面的代码A了。但事实上你拿(5 1 2 3 4 5)试试上面的代码,答案是(4),上面的代码给的是(5) 。因为(C_{frac{n}{2}})不是中位数,(n)为偶数时有两个中位数,所以出现了问题。

完全正确的代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1000000+2;
typedef long long ll;
ll a[maxn],sum,ave,c[maxn];
int n;
int main(){
    while(scanf("%d",&n)!=EOF){
        sum=0;
        for(int i=1;i<=n;i++) {
            scanf("%lld",&a[i]);
            sum+=a[i];
        }
        ave=sum/n;
        c[1]=a[1]-ave;
        for(int i=2;i<=n;i++) c[i]=c[i-1]+a[i]-ave;
        sort(c+1,c+1+n);
        ll mid=c[n/2];
        ll ans1=0;
        for(int i=1;i<=n;i++) ans1+=abs(mid-c[i]);
        mid=c[n/2+1];
        ll ans2=0;
        for(int i=1;i<=n;i++) ans2+=abs(mid-c[i]);
        printf("%lld
",min(ans1,ans2));
    }
    return 0;
}

幸甚至哉,歌以咏志。