首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/881/C
来源:牛客网
涉及:数学,gcd
点击这里回到2019牛客暑期多校训练营解题—目录贴
题目如下:
如题所示,题目想要得到那个最短距离的值,而不是P点的坐标,注意答案如果是整数就输出整数,否则输出分数
设距离为X,则
X
m
i
n
=
(
a
1
−
p
1
)
m
i
n
2
+
(
a
2
−
p
2
)
m
i
n
2
+
.
.
.
+
(
a
n
−
p
n
)
m
i
n
2
X_{min}=(a_1-p_1)^2_{min}+(a_2-p_2)^2_{min}+...+(a_n-p_n)^2_{min}
Xmin=(a1−p1)min2+(a2−p2)min2+...+(an−pn)min2
故需要求得
(
a
1
−
p
1
)
,
(
a
2
−
p
2
)
,
.
.
.
,
(
a
n
−
p
n
)
(a_1-p_1),(a_2-p_2),...,(a_n-p_n)
(a1−p1),(a2−p2),...,(an−pn)这一些列数的最小值
且:
1.
p
1
+
p
2
+
.
.
.
+
p
n
=
m
p_1+p_2+...+p_n=m
p1+p2+...+pn=m(注意,因为我没有把所有的
a
i
a_i
ai除以m,所以所有的p值加起来等于m,最后答案再除以m就ok)
2.由于
p
1
,
p
2
,
.
.
.
,
p
n
≥
0
p_1,p_2,...,p_n \ge0
p1,p2,...,pn≥0,故
(
a
1
−
p
1
)
≤
a
1
,
(
a
2
−
p
2
)
≤
a
2
,
.
.
.
,
(
a
n
−
p
n
)
≤
a
n
(a_1-p_1)\le a_1,(a_2-p_2)\le a_2,...,(a_n-p_n)\le a_n
(a1−p1)≤a1,(a2−p2)≤a2,...,(an−pn)≤an
也就是说,题目可以变成,把m的值分配给每一个
p
i
p_i
pi,让每一个
∣
a
i
−
p
i
∣
\left\vert a_i-p_i\right\vert
∣ai−pi∣尽可能的小,先对所有
a
i
a_i
ai有大到小排序,如图所示(n=5):
在分配的时候,我们想让每一个
a
i
a_i
ai都尽量的小,所以我们先将m分配给比较大的
a
i
a_i
ai(即排序后的
a
1
a_1
a1)
让比较大的
a
i
a_i
ai减小一个数b,其收益比让比较小的
a
i
a_i
ai减去一个数b的收益大,可以证明:
假设
a
1
a_1
a1比
a
2
a_2
a2大,由于收益(最小距离)是平方和状态,则明显
a
1
2
−
(
a
1
−
b
)
2
>
a
2
2
−
(
a
2
−
b
)
2
a_1^2-(a_1-b)^2>a_2^2-(a_2-b)^2
a12−(a1−b)2>a22−(a2−b)2
所以首先我们将m的值分配一些给
a
1
a_1
a1来减小,让
a
1
a_1
a1减小到和
a
2
a_2
a2一样大,于是
假设此时发现m分配一些值之后,m仍然没有等于0,此时还需要继续分配,但是,此时要将剩下的m的值同时分配给
a
1
,
a
2
a_1,a_2
a1,a2,让它们同时减小。
也可以证明同时减小比其中一个减小的收益高。
于是
到了此时,仍然有剩余的值,那就让
a
1
,
a
2
,
a
3
a_1,a_2,a_3
a1,a2,a3同时减小,但是发现,三个值减小到0的时候,m还有剩余的值,此时就需要将
a
1
,
a
2
,
a
3
a_1,a_2,a_3
a1,a2,a3减小到负数了,那么
∣
a
1
∣
∣
a
2
∣
∣
a
3
∣
\left\vert a_1\right\vert \left\vert a_2\right\vert \left\vert a_3\right\vert
∣a1∣∣a2∣∣a3∣的值会增大(此时的减小就成为了亏损),但是由于m的值必须全部分配完毕,所以必须得减小。
也可以证明当 a 1 , a 2 , . . , a n a_1,a_2,..,a_n a1,a2,..,an都小于0时,让它们同时减小的亏损比让某一个减少的亏损小。
假设此时m剩余的值,已经不足让
a
1
,
a
2
,
a
3
a_1,a_2,a_3
a1,a2,a3减小到
a
4
a_4
a4那么大,于是,在减小到某一值就会停止减小
此时,m=0,得到最短距离,距离为
a
1
2
+
a
2
2
+
.
.
+
a
5
2
m
2
\frac {a_1^2+a_2^2+..+a_5^2}{m^2}
m2a12+a22+..+a52
按照这种思路,不管n为多少,只要将m分配给所有最大的
a
i
a_i
ai,让他们同时减小,直到m=0,那么距离就为
a
1
2
+
a
2
2
+
.
.
+
a
n
2
m
2
\frac {a_1^2+a_2^2+..+a_n^2}{m^2}
m2a12+a22+..+an2
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,将
a
1
,
a
2
,
.
.
.
,
a
n
−
1
a_1,a_2,...,a_{n-1}
a1,a2,...,an−1都减小到了
a
n
a_n
an,发现此时m还有剩余的值,那就将
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an一起减小
此时刚好
a
1
=
a
2
=
.
.
.
=
a
n
,
i
=
n
a_1=a_2=...=a_n,i=n
a1=a2=...=an,i=n
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.首先将
a
1
a_1
a1减至
a
2
a_2
a2,此时m剩余8,如图
2.再将
a
1
,
a
2
a_1,a_2
a1,a2减到
a
3
a_3
a3,此时m剩余2
3.m还有剩余,
a
1
,
a
2
,
a
3
a_1,a_2,a_3
a1,a2,a3继续减小
于是最短距离为
(
−
8
3
)
2
⋅
3
1
0
2
=
16
75
\frac {(-\frac 8{3})^2·3}{10^2}=\frac {16}{75}
102(−38)2⋅3=7516
代码如下:
#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;
}