题意
环形排列的n(n≤106)个人,每人有一定量的金币。每个人可以给左右相邻的两个人金币,最终使得每个人都有相同量的金币。求被转手的最小金币数。
思路
贪心的经典题。
在n个人中:
- 设第
i 个人手中的金币数是Ai。- 设一个人最后手中的金币数是V,不难得出:
V=∑i=1n(Ai)×1n - 设第i个人给第
i−1 个人的金币数为Xi。
说明:
-> 如果是第i个人给第i−1 个人,Xi为正值,否则Xi为负值。
-> X1表示第1个人给第n个人的金币数。 - 设最终答案为
ans ,显然ans=∑i=1n|Xi|
不难发现,i的金币全部来自
i−1 或i+1,又因为每个人最终得到的金币数是V,所以说每一个人可以得到一个等式,对于第i 个人:V=Ai−Xi+Xi+1
上式变形可以得到:Xi+1=V−Ai+Xi
特殊的:- X2=V−A1+X1
- X3=V−A2+X2=(V−A1)+(V−A2)+X1
- X4=V−A3+X3=(V−A1)+(V−A2)+(V−A3)+X1
- ...
令Ci=∑ij=1(Aj−V),我们知道每一个Ci都是可求的。
则上式可以变成:- X2=X1−C1
- X3=X2−C2
- X4=X3−C3
- ...
于是ans=|X1|+∑n−1i=1|X1−Ci|
后面的式子的最小值可以转化求一个数轴上的中位数,于是问题的解。代码:
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> using namespace std; long long int n; long long int sum = 0; long long int ans = 0; const int maxn = 1e7+2; long long int a1[maxn]; long long int a2[maxn]; int main(){ while(cin >> n){ memset(a1, 0, sizeof a1); memset(a2, 0, sizeof a2); sum = ans = 0; for(int i = 1; i <= n; i ++){ scanf("%lld",&a1[i]); sum += a1[i]; } sum /= n; a2[0] = 0; for(int i = 1; i <= n; i ++){ a2[i] = a2[i-1] + a1[i] - sum; } sort(a2+1,a2+n+1); long long int t = a2[n/2 + 1]; for(int i = 1; i <= n; i ++){ ans += abs(t - a2[i]); } cout << ans << endl; } return 0; }
- 设一个人最后手中的金币数是V,不难得出: