bzoj1045: [HAOI2008] 糖果傳遞(數論)
阿新 • • 發佈:2018-02-20
line sum get 答案 ref 結果 algorithm 就是 abs
對於第一個小朋友,他給了第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個點,找出一個到他們的距離之和盡量小的點,而這個點就是這些數中的中位數。
這裏給出一點小小的證明:
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|。數軸上任意找一個點,它左邊有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] 糖果傳遞(數論)