参考算法竞赛入门经典训练指南
/*
最后每个人的金币:M = (A1+A2+...+An)/n(设当前每个人的金币为Ai)
设xi表示i给i+1传递给了xi个金币(xn表示n给1传递了xn个金币)
为什么只表示第i给人给第i+1个人的金币数量?因为就算是i+1给i x个金币也可以表示i给i+1 (-x)个金币
那么每个人都满足方程组:
A1+xn-x1 = M
A2+x1-x2 = M
A3+x2-x3 = M
...
An+xn-1-xn = M
而最后答案就是求min(x1+x2+...+xn)
x1 = A1+xn-M
x2 = A2+x1-M = A2+A1+xn-M-M=A1+A2+xn-2M
x3 = A3+x2-M = A3+A2+A1+xn-M-M-M=A1+A2+A3+xn-3M
...
xn = An+xn-1-M=A1+A2+...+An+xn-nM
设iM-(A1+A2+...+Ai)=Ci
那么x1+x2+...+xn =xn-C1+xn-C2+...+xn-Cn,因为无论如何传递的金币都是正数,因此可以加上绝对值
|xn-C1|+|xn-C2|+...+|xn-Cn|
那么就变成了数轴上找一个点到所有点距离最短(必须是整数)
那么那个点就是中位数,举一个简单的例子
在一个数轴上任意取6个点(不重合),假设你设定一个原点,左边有4个点,右边有2个点,那么你向左边移动d个单位(不超过左边第4个点),那么
该点到所有点的距离就会减少2d个单位(向左移d个单位左边的所有点的距离之和-4d,右边所有点的距离之和+2d),所以原点取在那里明显不行,因此
放在中位数的地方是最合适的(可以理解为最中间,即左边和右边都有3个点(可以包含),那么无论在那个区间怎么移动都是改变+3d和-3d最后结果不变)
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn = 1000000 + 10;
long long A[maxn], tot, M;//A为每个人的金币,tot为总金币,M为最后每个人应该持有的金币
vector<long long>C(maxn);//C为Ci = i*M - (A1+A2+...+Ai)(上述)
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int n;
while (cin>>n) {
memset(A, 0, sizeof(A));
C.resize(maxn);
tot = 0;
for (int i = 1; i <= n; i++) {
cin >> A[i];
tot += A[i];
}
M = tot / n;//计算M
long long cur = 0;
for (int i = 1; i <= n; i++) {
cur += A[i];
C[i] = i * M - cur;//计算Ci = i*M - (A1+A2+...+Ai)
}
sort(C.begin()+1,C.begin()+n+1);//排序以取中位数
long long xn = C[(1 + n) / 2];//中位数
long long res = 0;//结果
for (int i = 1; i <= n; i++) {
res += abs(xn - C[i]);//遍历每一个数轴上的点
}
cout << res << endl;//输出
}
return 0;
}

本文介绍了如何解决算法竞赛问题UVA11300,通过数学推导求解将金币均匀分配的最小调整策略。文章通过建立方程组,分析中位数的重要性,最终给出C++实现代码。
&spm=1001.2101.3001.5002&articleId=119567127&d=1&t=3&u=75b903aaeb214fcc96432778f3f2ca30)
3541

被折叠的 条评论
为什么被折叠?



