分金币 BZOJ 3293
分金币
【问题描述】
圆桌上坐着n个人,每人有一定数量的金币,金币总数能被n整除。每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数目相等。你的任务是求出被转手的金币数量的最小值。
【输入格式】
第一行为整数n(n>=3),以下n行每行一个正整数,按逆时针顺序给出每个人拥有的金币数。
【输出格式】
输出被转手金币数量的最小值。
【样例输入】
4
1
2
5
4
【样例输出】
4
【样例说明】
设四个人编号为1,2,3,4。第3个人给第2个人2个金币(变成1,4,3,4),第2个人和第4个人分别给第1个人1个金币。
【数据范围】
N<=<=100000,总金币数<=10^9
题解:
设a[i]为第i个人的初始金币数,
设p为a数组的平均数,
设c[i]为第i个人从第i-1个人拿到的金币数,那么:
将上列等式经过数学转换后可得:
C[i] = c[n] - (a[i] +···+ a[n]) + (n - i + 1)p,
答案即为
Ans = |c[1]| +···+ |c[n]|,
那么设
e[i] = - (a[i] +···+ a[n]) + (n - i + 1)p;,
因为a数组与p已知,所以e[i]已知,
那么答案
Ans = |e[1] - (-c[n])| +···+|e[n] - (-c[n])|,
观察式子可知答案即为在数轴上-c[n]分别到e[1] ~ e[n]的距离之和,
要使答案最小,则-c[n]取e数组的中位数时最优,最后统计答案。
1 #include<algorithm>
2 #include<iostream>
3 #include<cstring>
4 #include<cstdlib>
5 #include<cstdio>
6 #include<cmath>
7 using namespace std;
8 inline int Get()
9 {
10 int x = 0;
11 char c = getchar();
12 while('0' > c || c > '9') c = getchar();
13 while('0' <= c && c <= '9')
14 {
15 x = (x << 3) + (x << 1) + c - '0';
16 c = getchar();
17 }
18 return x;
19 }
20 int n;
21 long long cc;
22 long long sum;
23 long long ans;
24 long long c[100233];
25 long long a[100233];
26 int main()
27 {
28 n = Get();
29 for(int i = 1; i <= n; ++i)
30 {
31 a[i] = Get();
32 sum += a[i];
33 }
34 sum /= n;
35 a[0] = a[n];
36 for(int i = 1; i <= n; ++i)
37 c[i] = c[i - 1] + a[i - 1] - sum;
38 sort(c + 1, c + 1 + n);
39 cc = -c[(n >> 1) + 1];
40 for(int i = 1; i <= n; ++i) ans += abs(c[i] + cc);
41 printf("%lld", ans);
42 fclose(stdin), fclose(stdout);
43 }