https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2275
题目描述:环形排列的n
(n<=106)
个人,每人有一定量的金币。每个人可以给左右相邻的两个人金币,最终使得每个人都有相同量的金币。求被转手的最小金币数。
样例输入:
3
100
100
100
4
1
2
5
4
样例输出:
0
4
方法一:
进行数学分析:
设最终每个人有m个金币。第i个人给左边的人(第i-1个人)
xi
个金币。当然
xi
可以为负,这样表示第i-1个人给第i个人金币。
那么得到n-1个方程:
A1−x1+x2=m
——>
x2=m−A1+x1=x1−C1
(令
Ci=A1+A2+
…
+Ai−i∗m
)
A2−x2+x3=m
——>
x3=m−A2+x2=x1−C2
……
An−1−xn−1+xn=m
——>
xn=m−An−1+xn−1=x1−Cn−1
理论上来说应该还有一个方程式:
An−xn+x1=m
然而这是一个多余的方程式,因为总人数一定。
现在要求的就是
x1+|x1−C1|+|x1−C2|+
…
+|x1−Cn−1|
的最小值。当
x1
为中位数时,该值最小。(可以去百度一下零点分段法)
求中位数的算法可以用sort计算得到,也可以用快速选择算法(建议去看一看《算法导论》,上面讲的十分清楚)。
蒟蒻不才,想了半天还是看了题解才做出来的……
#include <iostream>
#include <cstdio>
#include <algorithm>
#define abs(a) ((a)>0?(a):(-(a)))
#define MAXN 1000005
#define LL long long int
using namespace std;
int n ;
LL money[MAXN] ,num[MAXN] ,ans ,result ,sum ;
int main()
{
while(~scanf("%d",&n))
{
sum=ans=0;
for(int i=1;i<=n;++i)
{
scanf("%lld",&num[i]);
sum+=num[i];
}
sum/=n;
for(int i=1;i<n;++i)
money[i]=money[i-1]+num[i]-sum;
sort(money,money+n);
result=money[n/2];
for(int i=0;i<n;++i)
ans+=abs(result-money[i]);
printf("%lld\n",ans);
}
return 0;
}
方法二:
令
xi
第
i
个人给左边的人(第
对其中一组解,每一个
实现:
1.找到任意一组可行解。
2.找到中位数
b
3.计算