分金币 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 }
posted @ 2017-01-03 15:04  草根柴鸡  阅读(152)  评论(0编辑  收藏  举报