1. 程式人生 > >BZOJ1045 [HAOI2008]糖果傳遞 && BZOJ3293 [Cqoi2011]分金幣

BZOJ1045 [HAOI2008]糖果傳遞 && BZOJ3293 [Cqoi2011]分金幣

Description

有n個小朋友坐成一圈,每人有ai個糖果。每人只能給左右兩人傳遞糖果。每人每次傳遞一個糖果代價為1。

Input

第一行一個正整數nn<=1'000'000,表示小朋友的個數. 接下來n行,每行一個整數ai,表示第i個小朋友得到的糖果的顆數.

Output

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

Sample Input

41254

Sample Output

4

Solution

數學題

1045和3293是重題所以就放一起了,其實還有lrj藍書上面的一道題也和這個一樣(UVA的)

首先我們設每個人最後擁有的糖果數為$m$

那麼很顯然這個$m$是可以求出來的,$$m=\frac{\sum _{i=1}^{i<=n}A_i}{n}$$

再設一下,$xi$代表每個人傳給了自己左邊的人$xi$個糖果(對於$x1$,代表第一個人傳給最後一個人$x1$個糖果(環形))

考慮第i個人,可以得到一個很顯而易見的方程:$A_i - x_i + x_{i+1} = m$

為什麼這個方程不用考慮左邊的人傳給這個人的情況?假設第$1$個人傳給第$2$個人$3$個糖果,第$2$個人傳給第$1$個人5個糖果,其實也就相當於,第$2$個人傳給第$1$個人$2$個糖果,所以是不用考慮這個情況的(如果$1$傳給$2$的牌比$2$傳給$1$的多,那麼$x2$則為負數)

同理可以得到一大堆的方程(其實就是把$1$~$n$分別代入上面的$i$)

我們可以嘗試著解方程

然後會發現這方程是解不出來的

但是我們發現了一個點,可以拿x1表示這一大堆的其他的xi

現在我們設一個C陣列,規定$C_i=C_{i-1}+A_i-m$

$a_1-x_1+x_2=m$化為$x_2=m-a_1+x_1=x_1-C_1$

同理,$a_2-x_2+x_3=m$化為

$x3$$=m-a2+x2$$=2*m-a2-a1+x1$$=x1-C1-a2+m$$=x1-C2$

於是我們就可以得到$n$個形似$xi=x1-Ci$的式子

好了我們在距離正解的路上已經邁出了一大步

考慮我們這$n-1$個等式能幹啥

想想題目,我們想要傳遞的糖果數量儘可能少,也就是說,我們要讓$\sum{abs(x)}$最小

然後再套一下之前的方程我們就可以把這個$\sum{abs(x)}$改寫成$\sum{abs(x_1)+abs(x_1-C_1)+abs(x_2-C_2)···+abs(x_1-C_{n})}$

於是現在的問題就變成了,我們需要一個$x1$讓$\sum{abs(x)}$最小

把這個玩意,對映到數軸上面,你就會發現一個神奇的東西

是的,就是中位數

怎麼證明?

隨便找一個點,假設這個點左邊的點的數量多於右邊的點的數量,那麼肯定不是最優的,要向左移動(設它的左邊有$l$個點,右邊有$r$個點)(如果它向左移動了$t$個單位長度,假設還沒碰到其他點,那麼它距離左邊的點少了$tl$的距離,距離右邊多了$tr$的距離,總的距離事實上減少了$(l-r)t$個單位長度的距離,更優)

所以,使這個$\sum{abs(x)}$最小的$x_1$,一定會是$C$陣列的中位數

所以得到這個$x1$之後,就可以推出這個$\sum{abs(x)}$的值了,答案也就出來了

嗯,這道題還卡$long \ long$,記得注意一下

挺好的一道數學題,挺思維的,程式碼難度也不大

#include <bits/stdc++.h>

using namespace std ;

#define ll long long
#define N 1000100

int n ;
ll a[ N ] , c[ N ] , sum = 0 , ans = 0 ;

int main() {
    scanf( "%d" , &n ) ;
    for( int i = 1 ; i <= n ; i ++ ) {
        scanf( "%lld" , &a[ i ] ) ;
        sum += a[ i ] ;
    }
    ll m = sum / n ;
    for( int i = 1 ;  i <= n ; i ++ ) {
        c[ i ] = c[ i - 1 ] + a[ i ] - m ;
    }
    sort( c + 1 , c + n + 1 ) ;
    ll t = c[ n & 1 ? ( n + 1 ) >> 1 : n >> 1 ] ;
    for( int i = 1 ; i <= n ; i ++ ) {
        ans += abs( c[ i ] - t ) ;
    }
    printf( "%lld\n" , ans ) ;
    return 0 ;
}