【来源】
HAOI2008
一本通题库-1432
BZOJ-1045
LibreOJ-10010
vjudge
【题目描述】
有 n n n个小朋友坐成一圈,每人有 a i a_i ai个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为 1 1 1。
【输入格式】
第一行一个正整数 n ≤ 1000000 n≤1000000 n≤1000000,表示小朋友的个数.
接下来 n n n行,每行一个整数 a i a_i ai,表示第 i i i个小朋友得到的糖果的颗数.
【输出格式】
求使所有人获得均等糖果的最小代价。
【输入样例】
4
1
2
5
4
【输出样例】
4
【数据范围】
对于 30% 的数据, n ≤ 1000 n≤1000 n≤1000;
对于 100% 的数据, n ≤ 1 0 6 n≤10^6 n≤106,保证答案可以用 64 位有符号整数存储。
【解析1】
贪心。
a
v
a
ava
ava用于计算平均数。
b
[
i
]
b[i]
b[i]是记录
i
i
i 给
i
+
1
i+1
i+1 的纸牌数。
一般的均分纸牌问题就相当于在第n个人与第1个人之间把环断开,此时这n个人站成一行,其持有的纸牌数、前缀和分别是:
纸牌数 | 前缀和 |
---|---|
a [ 1 ] a[1] a[1] | b [ 1 ] b[1] b[1] |
a [ 2 ] a[2] a[2] | b [ 2 ] b[2] b[2] |
… | … |
a [ n ] a[n] a[n] | b [ n ] b[n] b[n] |
如果在第 k k k 个人之后把环断开站成一行,这 n n n个人持有的纸牌数、前缀和分别是:
纸牌数 | 前缀和 |
---|---|
a [ k + 1 ] a[k+1] a[k+1] | b [ k + 1 ] − b [ k ] b[k+1]-b[k] b[k+1]−b[k] |
a [ k + 1 ] a[k+1] a[k+1] | b [ k + 2 ] − b [ k ] b[k+2]-b[k] b[k+2]−b[k] |
… | … |
a [ n ] a[n] a[n] | b [ n ] − b [ k ] b[n]-b[k] b[n]−b[k] |
a [ 1 ] a[1] a[1] | b [ 1 ] + b [ n ] − b [ k ] b[1]+b[n]-b[k] b[1]+b[n]−b[k] |
… | … |
a [ k ] a[k] a[k] | b [ k ] + b [ n ] − b [ k ] b[k]+b[n]-b[k] b[k]+b[n]−b[k] |
所以,所需最小花费为: ∑ i = 1 N ∣ s [ i ] − s [ k ] ∣ \sum_{i=1}^N {|s[i]-s[k]|} i=1∑N∣s[i]−s[k]∣
当 k k k 取何值时上式最小?显然,我们将b数组从小到大排序,取中位数作为 b [ k ] b[k] b[k]就是最优解。
【代码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
−
n
1-n
1−n号小朋友,可以视作
i
i
i号只向自己的右侧传递
x
i
x_i
xi个(向左的传递可以认为由
x
i
−
1
x_{i-1}
xi−1描述),
x
i
x_i
xi可正可负,则我们需要确定
x
1
,
x
2
,
…
…
,
x
n
x_1,x_2,……,x_n
x1,x2,……,xn,交换糖果次数为
S
=
Σ
∣
x
i
∣
S= \Sigma |x_i|
S=Σ∣xi∣
我们的目的是最小化
S
S
S。
事实上,考虑到
x
1
x_1
x1确定后,
x
2
x_2
x2的取值应恰好使
a
2
+
x
1
−
x
2
=
a
v
a
a_2+x_1-x_2=ava
a2+x1−x2=ava
这是由于除了
x
1
,
x
2
x_1,x_2
x1,x2,其他数不会影响
a
2
a_2
a2号的最终取值,变形得
x
2
=
a
2
+
x
1
−
a
v
a
x_2=a_2+x_1-ava
x2=a2+x1−ava
因此我们可以直接确定 x 2 x_2 x2,类似的 x 3 , x 4 , … … , x n x_3,x_4,……,x_n x3,x4,……,xn也可以依次确定,这需要 n n n次循环。我们只需要确定 x 1 x_1 x1就可以确定对应的 S S S。
容易发现, x 1 x_1 x1每增加 1 1 1, x 1 , x 2 , … … , x n x_1,x_2,……,x_n x1,x2,……,xn会各增加 1 1 1。我们因此得到两个结论:
(1) x 1 x_1 x1越大, x 1 , x 2 , … … , x n x_1,x_2,……,x_n x1,x2,……,xn中负数越少;
(2) 当 x 1 , x 2 , … … , x n x_1,x_2,……,x_n x1,x2,……,xn中负数多于 n 2 \frac{n}{2} 2n的时候,使 x 1 x_1 x1增加 1 1 1, S S S会减小(由于更多的 x i x_i xi是负的,它们的绝对值各减小了 1 1 1,更少的 x i x_i xi是负的,它们的绝对值会各增加 1 1 1);同理可得, x 1 , x 2 , … … , x n x_1,x_2,……,x_n x1,x2,……,xn中负数少于 n 2 \frac{n}{2} 2n时,使 x 1 x_1 x1减小1, S S S会减小。
根据(2),
x
1
x_1
x1使
x
1
,
x
2
,
…
…
,
x
n
x_1,x_2,……,x_n
x1,x2,……,xn中负数恰好为一半时,
S
S
S取到最小值;
根据(1),可以使用二分法找到这时的,复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
【代码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;
}