【來源】
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;
}