《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))

运行结果:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乐亦亦乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值