《Python—每日一道算法题》分金币(Spreading the Wealth,UVa 11300)

Description

圆桌旁坐着n个人,每人有一定数量的金币,金币总数能被n整除。每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数相等。你的任务是求出被转手的金币数量的最小值。比如,n=4,且4个人的金币数分别为1,2,5,4时,只需转移4枚金币(第3个人给第2个人两枚金币,第2个人和第4个人分别给第1 个人1枚金币)即可实现每人手中的金币数目相等。

Input

输入包含多组数据。每组数据第一行为整数n(n<=1 000 000),以下n行每行为一个整数,按逆时针顺序给出每个人拥有的金币数。输入结束标志为文件结束符(EOF)。

Output

对于每组数据,输出被转手金币数量的最小值。输入保证这个值在64位无符号整数范围内。

Sample Input

3
100
100
100
4
1
2
5
4


Sample Output

0
4

分析:

最终每个人的金币数量可以计算出来,它等于金币总数除以人数n。

使用M来表示每个人最终拥有的金币数。

假设有4个人,按顺序编号1,2,3,4。假设1号给2号3枚金币,然后2号又给1号5枚金币,实际上等价与2号给1号2枚金币,而1号什么也没给2号。

这样,可以设x_{2}表示2号给了1号多少金币。如果x_{2}<0,说明实际是1号给了2号-x_{2}枚金币。x_{1}x_{3}x_{4}含义类似。由于是环形,x_{1}指的是1号给4号多少金币。

现在假设编号为i的人初始有A_{i}枚金币。对于1号来说,他给了4号x_{1}枚金币,还剩A_{1}-x_{1}枚;但因为2号给了他x_{2}​​​​​​枚金币,所以最后还剩下A_{1}-x_{1}+x_{2}枚金币。根据题设,该金币数等于M。

A_{1}-x_{1}+x_{2}=M。

同理,对于第2个人,有A_{2}-x_{2}+x_{3}=M

最终,可以得到n个方程,一共有n个变量;但不可以直接解方程组,因为从前n-1个方程可以推导出最后一个方程。

可以尝试着用x_{1}表示出其他的x_{i},此题就变成了单变量的极值问题。

对于第1个人,A_{1}-x_{1}+x_{2}=M\rightarrow x_{2}=M-A_{1}+x_{1}=x_{1}-C_{1} (C_{1}=A_{1}-M)  下面类似。

对于第2个人,A_{2}-x_{2}+x_{3}=M\rightarrow x_{3}=M-A_{2}+x_{2}=2M-A_{1}-A_{2}+x_{1}=x_{1}-C_{2}

对于第3个人,A_{3}-x_{3}+x_{4}=M\rightarrow x_{4}=M-A_{3}+x_{3}=3M-A_{1}-A_{2}-A_{3}+x_{1}=x_{1}-C_{3}

……

对于第n个人,A_{n}-x_{n}+x_{1}=M  这是一个多余的等式,并不能给我们太多信息。

我们希望所有x_{i}的绝对值之和尽量小,即\left | x_{1} \right |+\left | x_{1}-C_{1} \right |+\left | x_{1}-C_{2} \right |+....+\left | x_{1}-C_{n-1} \right |最小。

注意\left | x_{1} -C_{1}\right |​​​​的几何意义是数轴上的点x_{i}C_{i}的距离。所以问题变成了:给定数轴上的n个点,找出一个到它们的距离之和尽量小的点。

***最优的x_{1}就是这些数的“中位数”。

证明:给定数轴上的n个点,在数轴上所有点中,中位数离所有顶点的距离之和最小。

任意找一个点,如图中灰色的点,它左边有2个输入点,右边有两个输入点。把它往左移动一点,不要移的太多,以免碰到输入点。假设移动了d单位距离,则灰色点左边4个点到它的距离各减少了d,右边点到它的距离各增加了d。总的来说 ,距离之和减少了2d。

如果灰色点的左边有2个点,右边有4个点。道理类似,不过应该向右移动。只要灰色点左右的输入点不一样多,就不是最优解。什么情况下左右的输入点一样多呢?如果输入点一共有奇数个,则灰色点必须和中间的那个点重合(中位数);如果有偶数个,则灰色点可以位于最中间的两个点之间的任意位置(还是中位数)。

Python 代码:

#初始化金币总数量
total=0
#每个人拥有的金币数存到列表A中
A=[0]
#输入人数n
n=int(input())
for i in range(n):
    a=int(input())
    total+=a
    A.append(a)
#每人最终拥有金币数M
M=total//n
C=[0]*1000000
for i in range(1,n):
    C[i]=C[i-1]+A[i]-M
#排序
C.sort()
#计算x1
x1=C[n//2]
ans=0
for i in range(n):
    ans+=abs(x1-C[i])#把x1代入,计算转手的金币总数
print("转手的金币总数为:{}".format(ans))

运行结果:

猜你喜欢

转载自blog.csdn.net/qq_41251963/article/details/109954046
今日推荐