有n个小朋友坐成一圈,每人有a[i]个糖果。
每人只能给左右两人传递糖果。
每人每次传递一个糖果代价为1。
求使所有人获得均等糖果的最小代价。
输入格式
第一行输入一个正整数n,表示小朋友的个数。
接下来n行,每行一个整数a[i],表示第i个小朋友初始得到的糖果的颗数。
输出格式
输出一个整数,表示最小代价。
数据范围
1≤n≤1000000
数据保证一定有解。
输入样例:
4
1
2
5
4
输出样例:
4
思路分析
设糖果平均数为a,ai代表第i堆糖果的个数,xi代表第i堆糖果向第i+1堆糖果(第n堆向第1堆传递)传递的糖果数目。温馨提示:xi代表是传递的差值(有很多种传递方案的)。
则
a1 - x1 + xn= a
a2 - x2 + x1 = a (含义:第1堆糖果向第2堆糖果传递了x1颗,第2堆糖果向第3堆糖果传递了x2颗,传递后第2堆糖果的数目等于糖果的平均数a)
…
an-1 - xn-1 + xn-2 = a
an - xn + xn-1 = a
∵有上面方程组等价于
x1 - xn=a1 - a
x2 - x1 = a2 - a
…
xn-1 - xn-2 = an-1 - a
xn + xn-1 =an - a
上面所有式子相加得0=0恒成立,所以上式中至少有一个多余方程。
∴令x1为自由变量,用x1表示其他的变量。
x1 = x1 + 0
x2= x1 + a2 - a
x3= x1 + a3 + a2 - 2a(由x3= x2 + a3 - a转化而来)
…
xn-1= x1 + an-1 + an-2 + … + a2 - (n-2)a
xn =x1 + an + an-1 + an-2 + … + a2 - (n-1)a
因为加法不好观察规律,所以将上式化为减法
x1 = x1 - 0
x2= x1 - (a - a2)
x3= x1 -(2a - a2 - a3)
…
xn =x1 - ((n-1)a - a2- a3 + a4 + … + an)
设 x1之后的部分为Ci
x1 = x1 - C1
x2= x1 - C2
x3= x1 - C3
…
xn =x1 - Cn
我们要求的目标是| x1 | + | x2 | + … +| xn |的最小值
即求| x1 - C1 | + | x1 - C2 | + … +| x1 - Cn |的最小值
∵| x1 - Cn | = | -(Cn - x1) | = | Cn - x1 |
∴ | Cn - x1 | + | x1 - C1 | ≥ |Cn - C1 |(温馨提示由| x | + | y | ≥ | x + y | 推导而来)
前后两两分组,取中位数即可取得最小值。(与货仓选址(邮递员问题)思路类似,看不懂的可以先看货仓选址)。
证明能取得最优解
∵ x1 取的是 Ci(1≤i≤n)中位数。
∴x1 - Ci肯定有一部分大于等于0,一部分小于等于0。
则必存在情况①xi ≥ 0且xi+1≤ 0 (对于第i+1堆糖果而言,都是别人给他或者他不用给别人),②xi ≤ 0且xi+1 ≥ 0 (对于第i+1堆糖果而言,都是他给别人或者他不用给别人)这两种情况先(给/收)左边这家还是先(给/收)右边这家,顺序无影响。所以能将环断开成链从而退化成均分纸牌问题(与均分纸牌思路类似,看不懂的可以先看均分纸牌)。注意点:别人给他,他再给下一家,这种顺序是有影响的。别人要先给他,他才够糖果给别人。
具体方案递归策略:
①xi ≥ 0且xi+1≤ 0 时先递归别人,最后别人再把糖果传递给他。
②xi ≤ 0且xi+1 ≥ 0 时他先把糖果传递给别人,最后再递归计算别人。
代码
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const int N=1000005;
LL s[N],c[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%lld",&s[i]);
s[i]+=s[i-1];//s[i]表示a[i]的前i项和
}
LL avg = s[n]/n;//求a[i]的平均数,因为s[n]是a的前n项和,所以直接除n即可
for(int i=2;i<=n;i++){//c[1]=0,所以直接从2开始计算即可
c[i] = (i-1)*avg - (s[i]-s[1]);//求c[i]=(i-1)*avg-(a[2]+a[3]+...+a[i])
}//参数:起始下标,选择下标,结束下标
nth_element(c+1,c+(n+1)/2,c+n+1);//求c数组的中位数,注意:下标从1开始
LL md = c[(n+1)/2];//将中位数赋值给变量md
LL res = 0;
for(int i =1;i<=n;i++){//统计差值,思路与货仓选址类似
res += abs(c[i]-md);
}
cout<<res<<endl;
return 0;
}