1. 程式人生 > >bzoj1045: [HAOI2008] 糖果傳遞(數論)

bzoj1045: [HAOI2008] 糖果傳遞(數論)

line sum get 答案 ref 結果 algorithm 就是 abs

1045: [HAOI2008] 糖果傳遞

題目:傳送門(雙倍經驗3293

題解:

   一開始想著DP貪心一頓亂搞,結果就GG了

十分感謝hzwer大佬寫的毒瘤數論題解:

  首先,最終每個小朋友的糖果數量可以計算出來,等於糖果總數除以n,用ave表示。   假設標號為i的小朋友開始有Ai顆糖果,Xi表示第i個小朋友給了第i-1個小朋友Xi顆糖果,如果Xi<0,說明第i-1個小朋友給了第i個小朋友Xi顆糖果,X1表示第一個小朋友給第n個小朋友的糖果數量。 所以最後的答案就是ans=|X1| + |X2| + |X3| + ……+ |Xn|。
  對於第一個小朋友,他給了第n個小朋友X1顆糖果,還剩A1-X1顆糖果;但因為第2個小朋友給了他X2顆糖果,所以最後還剩A1-X1+X2顆糖果。根據題意,最後的糖果數量等於ave,即得到了一個方程:A1-X1+X2=ave。   同理,對於第2個小朋友,有A2-X2+X3=ave。最終,我們可以得到n個方程,一共有n個變量,但是因為從前n-1個方程可以推導出最後一個方程,所以實際上只有n-1個方程是有用的。   盡管無法直接解出答案,但可以用X1表示出其他的Xi,那麽本題就變成了單變量的極值問題。   對於第1個小朋友,A1-X1+X2=ave -> X2=ave-A1+X1 = X1-C1(假設C1=A1-ave,下面類似)
  對於第2個小朋友,A2-X2+X3=ave -> X3=ave-A2+X2=2ave-A1-A2+X1=X1-C2   對於第3個小朋友,A3-X3+X4=ave -> X4=ave-A3+X3=3ave-A1-A2-A3+X1=X1-C3……   對於第n個小朋友,An-Xn+X1=ave。   我們希望Xi的絕對值之和盡量小,即|X1| + |X1-C1| + |X1-C2| + ……+ |X1-Cn-1|要盡量小。註意到|X1-Ci|的幾何意義是數軸上的點X1到Ci的距離,所以問題變成了:給定數軸上的n個點,找出一個到他們的距離之和盡量小的點,而這個點就是這些數中的中位數。
     這裏給出一點小小的證明:

  數軸上任意找一個點,它左邊有4個點,右邊有2個點,把該點往左移動一點點,不要移動太多,以免碰到其他輸入點。假設移動了d單位距離,則該點到左邊4個點的距離各減少d,該點都右邊2個點的距離各增加d,但總的來說,距離之和減少了2d。

  同理,該點的左邊有2個點,右邊有4個點時,類似,不過此時應該是向右移動。

  換句話說,只要該點的左右兩邊的輸入點個數不一樣多,就不是最優解。那什麽情況下,左右點一樣多勒?如果輸入點有奇數個,則最優解應該是中間那個點即中位數。如果有偶數個,則可以位於最中間兩個點的任意位置(還是中位數)。

代碼:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cstdlib>
 4 #include<cmath>
 5 #include<algorithm>
 6 using namespace std;
 7 typedef long long LL;
 8 int n;LL a[1100000],c[1100000],sum;
 9 int main()
10 {
11     scanf("%d",&n);sum=0;
12     for(int i=1;i<=n;i++)scanf("%lld",&a[i]),sum+=a[i];sum/=n;
13     memset(c,0,sizeof(c));for(int i=1;i<=n;i++)c[i]=c[i-1]+a[i]-sum;
14     sort(c+1,c+n+1);LL mid;
15     if(n&1)mid=c[(n+1)/2];else mid=c[n/2];
16     LL ans=0;for(int i=1;i<=n;i++)ans+=abs(c[i]-mid);
17     printf("%lld\n",ans);
18     return 0;
19 }

bzoj1045: [HAOI2008] 糖果傳遞(數論)