【C++】「一本通 1.1 練習 6」「HAOI-2008」糖果傳遞

【來源】

HAOI2008
一本通題庫-1432
BZOJ-1045
LibreOJ-10010
vjudge

【題目描述】

nn個小朋友坐成一圈,每人有aia_i個糖果。每人只能給左右兩人傳遞糖果。每人每次傳遞一個糖果代價爲11

【輸入格式】

第一行一個正整數n1000000n≤1000000,表示小朋友的個數.

接下來nn行,每行一個整數aia_i,表示第ii個小朋友得到的糖果的顆數.

【輸出格式】

求使所有人獲得均等糖果的最小代價。

【輸入樣例】

4
1
2
5
4

【輸出樣例】

4

【數據範圍】

對於 30% 的數據,n1000n≤1000

對於 100% 的數據,n106n≤10^6,保證答案可以用 64 位有符號整數存儲。

【解析1】

貪心。
avaava用於計算平均數。
b[i]b[i]是記錄 iii+1i+1 的紙牌數。

一般的均分紙牌問題就相當於在第n個人與第1個人之間把環斷開,此時這n個人站成一行,其持有的紙牌數、前綴和分別是:

紙牌數 前綴和
a[1]a[1] b[1]b[1]
a[2]a[2] b[2]b[2]
a[n]a[n] b[n]b[n]

如果在第 kk 個人之後把環斷開站成一行,這 nn個人持有的紙牌數、前綴和分別是:

紙牌數 前綴和
a[k+1]a[k+1] b[k+1]b[k]b[k+1]-b[k]
a[k+1]a[k+1] b[k+2]b[k]b[k+2]-b[k]
a[n]a[n] b[n]b[k]b[n]-b[k]
a[1]a[1] b[1]+b[n]b[k]b[1]+b[n]-b[k]
a[k]a[k] b[k]+b[n]b[k]b[k]+b[n]-b[k]

所以,所需最小花費爲:i=1Ns[i]s[k] \sum_{i=1}^N {|s[i]-s[k]|}

kk 取何值時上式最小?顯然,我們將b數組從小到大排序,取中位數作爲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。

1n1-n號小朋友,可以視作ii號只向自己的右側傳遞xix_i個(向左的傳遞可以認爲由xi1x_{i-1}描述),xix_i可正可負,則我們需要確定x1,x2,,xnx_1,x_2,……,x_n,交換糖果次數爲
S=ΣxiS= \Sigma |x_i|
我們的目的是最小化SS
事實上,考慮到x1x_1確定後,x2x_2的取值應恰好使a2+x1x2=avaa_2+x_1-x_2=ava
這是由於除了x1,x2x_1,x_2,其他數不會影響a2a_2號的最終取值,變形得x2=a2+x1avax_2=a_2+x_1-ava

因此我們可以直接確定x2x_2,類似的x3,x4,,xnx_3,x_4,……,x_n也可以依次確定,這需要nn次循環。我們只需要確定x1x_1就可以確定對應的SS

容易發現,x1x_1每增加11x1,x2,,xnx_1,x_2,……,x_n會各增加11。我們因此得到兩個結論:

(1) x1x_1越大,x1,x2,,xnx_1,x_2,……,x_n中負數越少;

(2) 當x1,x2,,xnx_1,x_2,……,x_n中負數多於n2\frac{n}{2}的時候,使x1x_1增加11SS會減小(由於更多的xix_i是負的,它們的絕對值各減小了11,更少的xix_i是負的,它們的絕對值會各增加11);同理可得,x1,x2,,xnx_1,x_2,……,x_n中負數少於n2\frac{n}{2}時,使x1x_1減小1,SS會減小。

根據(2),x1x_1使x1,x2,,xnx_1,x_2,……,x_n中負數恰好爲一半時,SS取到最小值;
根據(1),可以使用二分法找到這時的,複雜度爲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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章