首先發出題目鏈接:
鏈接:https://ac.nowcoder.com/acm/contest/881/C
來源:牛客網
涉及:數學,gcd
點擊這裏回到2019牛客暑期多校訓練營解題—目錄貼
題目如下:
如題所示,題目想要得到那個最短距離的值,而不是P點的座標,注意答案如果是整數就輸出整數,否則輸出分數
設距離爲X,則
故需要求得這一些列數的最小值
且:
1.(注意,因爲我沒有把所有的除以m,所以所有的p值加起來等於m,最後答案再除以m就ok)
2.由於,故
也就是說,題目可以變成,把m的值分配給每一個,讓每一個儘可能的小,先對所有有大到小排序,如圖所示(n=5):
在分配的時候,我們想讓每一個都儘量的小,所以我們先將m分配給比較大的(即排序後的)
讓比較大的減小一個數b,其收益比讓比較小的減去一個數b的收益大,可以證明:
假設比大,由於收益(最小距離)是平方和狀態,則明顯
所以首先我們將m的值分配一些給來減小,讓減小到和一樣大,於是
假設此時發現m分配一些值之後,m仍然沒有等於0,此時還需要繼續分配,但是,此時要將剩下的m的值同時分配給,讓它們同時減小。
也可以證明同時減小比其中一個減小的收益高。
於是
到了此時,仍然有剩餘的值,那就讓同時減小,但是發現,三個值減小到0的時候,m還有剩餘的值,此時就需要將減小到負數了,那麼的值會增大(此時的減小就成爲了虧損),但是由於m的值必須全部分配完畢,所以必須得減小。
也可以證明當都小於0時,讓它們同時減小的虧損比讓某一個減少的虧損小。
假設此時m剩餘的值,已經不足讓減小到那麼大,於是,在減小到某一值就會停止減小
此時,m=0,得到最短距離,距離爲
按照這種思路,不管n爲多少,只要將m分配給所有最大的,讓他們同時減小,直到m=0,那麼距離就爲
sort(a+1,a+n+1,greater<int>());//由大到小排序
for(i=1;i<n;i++){
int x=a[i]-a[i+1];//每一個a[i]與a[i+1]之間的差值
if(tot+x*i<=m) tot+=x*i;//tot是m已經分配的值,tot+x*i用來判斷m還能不能分配值,使a[1],..,a[i]到a[i+1]的位置
else break;//不能剛好分配
}
int rem=m-tot; //m剩餘的值
//fz/fm等於前面a[1],...,a[i]最小能減少到多少
ll fz=i*a[i]-rem;
ll fm=m*i;
ll gcd=getgcd(fz*fz*i,fm*fm);
fz=fz*fz*i/gcd;fm=fm*fm/gcd;//(fz*fz*i)/(fm*fm)是前面a[1],...,a[i]對距離的貢獻
for(i=i+1;i<=n;i++) get(fm,fz,m,a[i]);//加上後面沒有分配到值的a[i+1],..,a[n]對距離的貢獻(a[i]*a[i])/m*m
void get(ll &fm,ll &fz,ll m,ll z){//分數加法函數
ll gcd=getgcd(fz*m*m+z*z*fm,fm*m*m);
fz=(fz*m*m+z*z*fm)/gcd;
fm=fm*m*m/gcd;
return;
}
當然,排序後分配m,將都減小到了,發現此時m還有剩餘的值,那就將一起減小
此時剛好
fz=n*a[n]-rem;
fm=m*n;
ll gcd=getgcd(fz*fz*n,fm*fm);
fz=fz*fz*n/gcd;fm=fm*fm/gcd;
舉個例子
n=3,m=10,a={1,-2,3}
1.首先將減至,此時m剩餘8,如圖
2.再將減到,此時m剩餘2
3.m還有剩餘,繼續減小
於是最短距離爲
代碼如下:
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
int n,m,a[maxn];//題目所給變量
ll getgcd(ll a,ll b){return (b==0?a:getgcd(b,a%b));}
void get(ll &fm,ll &fz,ll m,ll z){//分數加法函數
ll gcd=getgcd(fz*m*m+z*z*fm,fm*m*m);
fz=(fz*m*m+z*z*fm)/gcd;
fm=fm*m*m/gcd;
return;
}
int main(){
while(~scanf("%d%d",&n,&m)){
int i,tot=0;
for(i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+n+1,greater<int>());//由大到小排序
for(i=1;i<n;i++){
int x=a[i]-a[i+1];//每一個a[i]與a[i+1]之間的差值
if(tot+x*i<=m) tot+=x*i;//tot是m已經分配的值,tot+x*i用來判斷m還能不能分配值,使a[1],..,a[i]到a[i+1]的位置
else break;//不能剛好分配
}
int rem=m-tot;//m剩餘的值
//fz/fm等於前面a[1],...,a[i]最小能減少到多少
ll fz=i*a[i]-rem;
ll fm=m*i;
ll gcd=getgcd(fz*fz*i,fm*fm);
fz=fz*fz*i/gcd;fm=fm*fm/gcd;//約分
//(fz*fz*i)/(fm*fm)是前面a[1],...,a[i]對距離的貢獻
for(i=i+1;i<=n;i++) get(fm,fz,m,a[i]);//加上後面沒有分配到值的a[i+1],..,a[n]對距離的貢獻(a[i]*a[i])/m*m
//下面是輸出限制
if(fm==1) printf("%lld\n",fz);
else printf("%lld/%lld\n",fz,fm);
}
return 0;
}