#2019121000025-LGTD

T112394 分金币

题目描述

圆桌周围坐了(n)个人,每个人有一定数量的金币,金币的总数保证是(n)的倍数,每个人可以向左或向右给出一些金币,使得最终每个人的金币数相等。你的任务是求出被转移了的金币的总数。

比如,(n=4)时,如果四个人的金币数是(1,2,5,4),则转移4枚金币(第3个人给第2个人2枚,第2个人和第4个人分别给第1个人1枚金币)即可达到目的。

输入格式

包含多组数据。第一行是测试组数(T),接下来每组数据第一行为一个整数(n),以下(n)行每行为一个整数,按逆时针顺序给出每个人拥有的金币数。

输出格式

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

输入输出样例

输入

2
3
100
100
100
4
1
2
5
4

输出

0
4

说明/提示

(1leq T leq 10, nleq 10^6)

标程时间复杂度:(O(Tnlogn))

题解

对于在座的(n)个人,记每个人现在拥有的金币数是(A_i(iin [1,n])),则金币总数(s=sum^{n}_{i=1}A_i),每个人最终得到的金币数$$y=frac{s}{n}$$
如果1号给2号1枚,而2号给1号2枚,就相当于2号给1号1枚,所以我们用(x_2)表示2号给1号了多少金币,特别地,如果(x_2<0),实际上是1号给2号(-x_2)枚金币。注意,由于是环形,所以(x_1)表示1号给(n)号了几枚金币。

如果有四个人,依次编号(1,2,3,4),对于1号来说,他给了4号(x_1)枚金币,但是2号又给了他(x_2)枚,我们最终得到1号最终的金币数$$y=A_1-x_1+x_2$$

同理,对于第2个人,有(y=A_2-x_2+x_3),我们一共得到了(n)个方程,很可惜,我们不能解,因为前(n-1)个式子能推导出第(n)个,所以只有(n-1)个式子是有用的。

尽管这样,我们还是想用(x_1)来表示其他的(x_i),有:对于第1个人,(A_1-x_1+x_2=y),于是$$x_2=y-A_1+x_1=x_1-C_1$$

其中我们规定:$$C_i=A_i-y$$则(C_1=A_1-y)

对于第2个人,(A_2-x_2+x_3=y),于是$$x_3=y-A_2+x_2=2y-A_1-A_2+x_1=x_1-C_2$$

对于第2个人,(A_3-x_3+x_4=y),于是$$x_4=y-A_3+x_3=3y-A_1-A_2-A_3+x_1=x_1-C_3$$

如此推导下去,我们可以得到若干等式。我们的目的是计算所有(x_i)的绝对值之和最小,即:$$(|x_1|+|x_1-C_1|+|x_1-C_2|+...+|x_1-C_{n-1}|)_{min}$$

注意(|x_i-C_i|)的几何意义是数轴上点(x_i)(C_i)的距离,问题变成了:给定数轴上的(n)个点,求一个到他们的距离之和尽量小的点。

结论是:这个点是这(n)个数的中位数。所以只需排序即可。严谨的证明如下:

在数轴上标出这(n)个点,假定数轴上标好了六个点,从左到右编号(C_i(iin {1,2,3,4,5,6})),如果选定(C_4,C_5)之间的一点(P),之后把这个点向左(或者向右,效果相同,假定向左)(D)个单位长度,则(P')到它左边的四个点距离减少了(4D),到右边的两个点距离增加了(2D),总的来说距离减小了(2D)

通过这样的移动,发现在奇数个数构成的数列,满足条件的数是最中间的数,而偶数个数构成的数列是最中间两个数的平均数,得证。

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1000000+10;
long long A[maxn],C[maxn],tot,M;
int T;
int n;
int main(){
	//freopen("coin10.in","r",stdin); 
	//freopen("coin10.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		tot=0;
		memset(A,0,sizeof(A));
		memset(C,0,sizeof(C));
		for(int i=1;i<=n;i++){
			scanf("%lld",&A[i]);
			tot+=A[i];
		}
		M=tot/n;
		C[0]=0;
		for(int i=1;i<n;i++){
			C[i]=C[i-1]+A[i]-M;
		}
		sort(C,C+n);
		long long x1=C[n/2],ans=0;
		for(int i=0;i<n;i++){
			ans+=abs(x1-C[i]);
		}
		printf("%lld
",ans);
	}
	return 0;
} 
要做就做南波万
原文地址:https://www.cnblogs.com/liuziwen0224/p/12018969.html