A Communist regime is trying to redistribute wealth in a village. They have have decided to sit everyone around a circular table. First, everyone has converted all of their properties to coins of equal value, such that the total number of coins is divisible by the number of people in the village. Finally, each person gives a number of coins to the person on his right and a number coins to the person on his left, such that in the end, everyone has the same number of coins. Given the number of coins of each person, compute the minimum number of coins that must be transferred using this method so that everyone has the same number of coins.
There is a number of inputs. Each input begins with n(n<1000001), the number of people in the village. n lines follow, giving the number of coins of each person in the village, in counterclockwise order around the table. The total number of coins will fit inside an unsigned 64 bit integer.
For each input, output the minimum number of coins that must be transferred on a single line.
3 100 100 100 4 1 2 5 4
0 4
题目大意:圆桌旁边坐着n个人,每人有一定数量的金币,金币总数能被n整除。每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数目相等。你的任务是求出被转手的金币数量的最小值。比如n=4,且4个人的金币数量分别为1,2,5,4时,只需转移4枚金币(第3个人给第2个人2枚金币,第2个人和第4个人分别给第1个人1枚金币)即可实现每人手中的金币数目相等。
分析:首先最终每个人的金币数量可以计算出来,它等于金币总数除以人数n。接下来用M来表示每个人最终拥有的金币数。
现在假设编号为 i 的人初始有Ai 枚金币,对于1号来说,他给了4号x1枚金币,还剩Ai -x1枚金币;但是2号给了他x2枚金币,所以还剩A1-x1+x2枚金币。所以A1-x1+x2=M。同理对于第2个人,有A2-x2+x3=M。最终得到n个方程,实际上只有n-1个有用。
尝试用x1表示出其他的xi ,则本题就变成了单变量的极值问题。
对于第1个人,A1-x1+x2=M →x2=M-A1+x1=x1-C1(规定C1=A1-M,下面类似)
对于第2个人,A2-x2+x3=M →x3=M-A2+x2=2M-A1-A2+x1=x1-C2
对于第3个人,A3-x3+x4=M →x4=M-A3+x3=3M-A1-A2-A3+x1=x1-C3
...
对于第n个人,An-xn+x1=M。这是一个多余的等式。
我们希望所有xi 的绝对值之和尽量小,即|x1|+|x1-C1|+|x1-C2|+...+|x1-Cn-1|要最小。注意到|x1-Ci|的几何意义是数轴上的点x1到Ci 的距离,所以问题变成了:给定数轴上n个点,找出一个到他们距离之和尽量小的点。
而这个点就是中位数,它实在是太优美,太巧妙了,而且不少其他问题也能用的上,接下来要证明它。
证明:给定数轴上的n个点,在数轴上的所有点中,中位数离所有顶点的距离之和最小。
在有6个点的数轴上,任取一点,比如最左边和最右边点的中点K,它的左边有4个点,右边有2个点。把它往左移一点,不要移太多,以免碰到输入点。假设移动的d单位距离,则这个点K左边4个点到它的距离各减少了d,右边的2个点到它的距离各增加了d,但总的来说,距离之和减少了2d。如果K的左边有2个点,右边有4个点,道理类似,不过应该向右移动。
换句话说,只要灰点左右的输入点不一样多,就不是最优解。所以,如果输入点一共有奇数个,则灰点必须和中间的那个点重合(中位数);如果有偶数个,则灰点可以位于最中间两个点之间的任意位置(还是中位数)。
代码如下:
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 5 const int maxn = 1000000 + 10; 6 long long A[maxn], C[maxn], tot, M; 7 int main() { 8 int n; 9 while(scanf("%d", &n) == 1) { // 输入数据大,scanf比cin快 10 tot = 0; 11 for(int i = 1; i <= n; i++) { scanf("%lld", &A[i]); tot += A[i]; } // 用%lld输入long long 12 M = tot / n; 13 C[0] = 0; 14 for(int i = 1; i < n; i++) C[i] = C[i-1] + A[i] - M; // 递推C数组 15 sort(C, C+n); 16 long long x1 = C[n/2], ans = 0; // 计算x1 17 for(int i = 0; i < n; i++) ans += abs(x1 - C[i]); 18 // 把x1代入,计算转手的总金币数 19 printf("%lld\n", ans); 20 } 21 return 0; 22 }
另外%lld这个占位符不是跨平台的。虽然cin/cout没有这个问题,但是本题输入量比较大,cin/cout会比较慢。