圆桌旁坐着n个人,每人有一定数量的金币,金币总能被n整除。每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数目相等。你的任务是求出被转手的金币数量的最小值。比如,n=4,且4个人的金币数量分别为1,2,5,4时,只需转移4枚金币(第3个人给第二个人两枚金币,第二个人和第四个人分别给第一个人1枚金币)即可实现每人手中的金币数目相等。
输入格式:
#include<stdio.h>
#include<math.h>
void sort(__int64 *a,__int64 I){
int val,i,j;
for(i=0;i<I-1;i++)
for(j=i+1;j<I;j++){
if(a[i]<a[j]){
val=a[i];
a[i]=a[j];
a[j]=val;
}
}
}
int main(){
__int64 n;
while(scanf("%I64d\n",&n) == 1){
__int64 i,a[n],sum=0;
for(i=n;i>0;i--)//输入
{
scanf("%I64d\n",&a[i]);
sum+=a[i];
}
__int64 avg=sum/n,c[n];
c[0]=0;
for(i=0;i<n;i++)
{
c[i]=c[i-1]+a[i]-avg;
}
sort(c,n);
__int64 medium=c[n/2],ans=0;
for(i=0;i<n;i++){
ans+=abs(medium-c[i]);
}
printf("%I64d\n",ans);
}
return 0;
}
输入包含多组数据。每组数据第一行为整数n(n<=1000000),以下n行每行为一个整数,按逆时针顺序给出每个人拥有的金币数。输入结束标志为文件结束符(EOF)
输出格式:
对于每组数据,输出被转手金币数量的最小值。输入保证这个值在六十四位无符号整数范围内。
分析:
这道题目看起来很复杂,让我们慢慢分析。首先,最终每个人的金币数量可以计算出来,它等于金币总数除以人数n。接下来我们用avg来表示每人最终拥有的金币数。
假设有四个人,按顺序编号为1,2,3,4.假设1号给2号3枚金币,然后2号又给1号5枚金币。这等价于2号给1号2枚金币,而1号什么也没给2号。这样可以设置x2表示2号给了1号多少个金币。如果x2<0,说明实际上是1号给了2号-x2枚金币。x1,x3和x4的含义类似。注意,由于是环形,x1指的是1号给4号多少金币。
现在假设编号为i的人初始有ai枚金币。对于1号来说,他给了4号x1枚金币,还剩ai-x1枚金币;但因为2号给了他x2枚金币,所以最后还剩ai-x1+x2枚金币。根据题设,该金币数等于avg。
同理,对于第二个人,有a2-x2+x3=avg.最终,我们可以得到n个方程,一共有n个变量,是不是可以直接解方程组了呢?很可惜,还不行。因为从前n-1个方程可以推导出最后一个方程。所以,实际上只有n-1个方程是有用的。
尽管无法直接解出答案,我们还是可以尝试着用x1表示出其他的xi,则本题就变成了单变量的极值问题。
对于第一个人,a1-x1+x2=avg->x2=avg-a1+x1=x1-c1
对于第二个人,a2-x2+x3=avg->x3=avg-a2+x2=avg-a2+age-a1+x1=x1-c2
......
我们希望所有xi的绝对值之和尽量小,即|x1|+|x1-c1|+|x1-c2|+...+|x1-cn-1|要最小。注意到|x1-ci|的几何意义是数轴上点x1到ci的距离。所以问题变成了:给定数轴上的n个点,找出一个到他们的距离之和尽量小的点。
该点便是中位数
证明:
给定数轴上的n个点,在数轴的所有点中,中位数离所有顶点的距离之和最小。凡是能转化为这个模型的题目都可以用中位数求解,并不只适用于本题。
任意找一个点,假设它左边有四个输入点,右边有两个输入点。把它往左移动一点,不要移的太多,以免碰到输入点,假设移动了d单位距离,则该点左边4个点到它的距离各减少了d,右边的两个点到它的距离各增加了d。但总的来说,距离之和减少了2d。
如果该点左边有两个点,右边有四个点,道理类似,不过应该向右移动。换句话说,只要该点左右的输入点不一样多,就不是最优解。什么情况下左右的输入点一样多呢?如果输入点一共有奇数个,则该点必须与中间的那个点重合(中位数);如果有偶数个,则该点可以位于最中间的两个点之间的任意位置(还是中位数)