题意
环形排列的 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
,不难得出: