【来源】
HAOI2008
一本通题库-1432
BZOJ-1045
LibreOJ-10010
vjudge
【题目描述】
有个小朋友坐成一圈,每人有个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为。
【输入格式】
第一行一个正整数,表示小朋友的个数.
接下来行,每行一个整数,表示第个小朋友得到的糖果的颗数.
【输出格式】
求使所有人获得均等糖果的最小代价。
【输入样例】
4
1
2
5
4
【输出样例】
4
【数据范围】
对于 30% 的数据,;
对于 100% 的数据,,保证答案可以用 64 位有符号整数存储。
【解析1】
贪心。
用于计算平均数。
是记录 给 的纸牌数。
一般的均分纸牌问题就相当于在第n个人与第1个人之间把环断开,此时这n个人站成一行,其持有的纸牌数、前缀和分别是:
纸牌数 | 前缀和 |
---|---|
… | … |
如果在第 个人之后把环断开站成一行,这 个人持有的纸牌数、前缀和分别是:
纸牌数 | 前缀和 |
---|---|
… | … |
… | … |
所以,所需最小花费为:
当 取何值时上式最小?显然,我们将b数组从小到大排序,取中位数作为就是最优解。
【代码1】
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
int const N=1e6+5;
int n;
LL ava,ans;
LL a[N],b[N];
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%lld",&a[i]);
ava+=a[i];
}
ava=ava/n;
b[1]=a[1]-ava;
for(int i=2; i<=n; i++) b[i]=b[i-1]+a[i]-ava;
sort(b+1,b+n+1);
for(int i=1; i<=n; i++) ans+=abs(b[i]-b[(n+1)/2]);
printf("%lld\n",ans);
return 0;
}
【解析2】
二分。
此解析来自LibreOJ,原文链接
我们可以先扫一遍,计算糖果数的平均值ava,我们的目的是使每个同学的糖果数变为ava。
对号小朋友,可以视作号只向自己的右侧传递个(向左的传递可以认为由描述),可正可负,则我们需要确定,交换糖果次数为
我们的目的是最小化。
事实上,考虑到确定后,的取值应恰好使
这是由于除了,其他数不会影响号的最终取值,变形得
因此我们可以直接确定,类似的也可以依次确定,这需要次循环。我们只需要确定就可以确定对应的。
容易发现,每增加,会各增加。我们因此得到两个结论:
(1) 越大,中负数越少;
(2) 当中负数多于的时候,使增加,会减小(由于更多的是负的,它们的绝对值各减小了,更少的是负的,它们的绝对值会各增加);同理可得,中负数少于时,使减小1,会减小。
根据(2),使中负数恰好为一半时,取到最小值;
根据(1),可以使用二分法找到这时的,复杂度为。
【代码2】
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define RI register int
#define re(i,a,b) for(RI i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(a,b) (((a)>(b)) ? (a):(b))
#define MIN(a,b) (((a)<(b)) ? (a):(b))
using namespace std;
typedef long long LL;
const int N=1e6+5;
const LL inf=9e18;
int n;
LL ava,ans,sum;
LL a[N],b[N];
LL getcnt(LL x1) {
LL cnt=(x1<0);
for(int i=2; i<=n; i++) {
x1=x1+a[i]-ava;
cnt+=(x1<0);
}
return cnt;
}
LL getS(LL x1) {
LL S=abs(x1);
for(int i=2; i<=n; i++) {
x1=x1+a[i]-ava;
S+=abs(x1);
}
return S;
}
//l使x[]中负数不少于(n/2),r使得x[]中负数多于(n/2)(下取整)
//这是左闭右开区间,结束时l,r相差1,无论n的奇偶,r为所求的最佳x1
void bs(LL l,LL r) {
if(l+1==r) {
ans=getS(r);
return;
}
LL mid=(l+r)>>1;
if(getcnt(mid)>=(n>>1)) bs(mid,r);
else bs(l,mid);
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%lld",&a[i]);
ava+=a[i];
}
ava=ava/n;
bs(-inf,inf);
printf("%lld\n",ans);
return 0;
}