这道题的原题意给出了约束条件,要求函数极值。
这里可以用大一下册的拉格朗日乘子法的知识。过程比较复杂,这里贴一个B站的讲解视频(不是我的),讲得很详细。
参考来源:https://www.bilibili.com/video/av59874180/
过程比较复杂,这里讲一个更容易些的做法。
题意转换
首先对题意进行转换
给你一个点A,数列a1 ~ an表示A点在n维的空间里的坐标,还有一个数字m,然后要你找到点P,要求点P满足以下条件:
1、P1 ~ Pn 都是 >= 0 的实数
2、P1 ~ Pn 累加 == m
3、P点到题目给出的点A的距离最短(转换成这样是因为在原题意的式子上乘了个m^2进去)
也
就
是
说
转
换
题
意
后
,
我
们
要
使
∑
i
=
1
n
(
a
i
−
p
i
)
2
最
小
也就是说转换题意后,我们要使\sum_{i = 1}^{n} (a_i -p_i)^2 最小
也就是说转换题意后,我们要使i=1∑n(ai−pi)2最小
然后算出这个式子的最小值后再除以m2 即可得到正解。(然而并不是,想要跑的快,还要注意精度问题。当然最后分情况讨论,然后多跑一遍就不用了。)
思路
我们要使得减去pi后的ai平方和最小,那么ai中较大的数减小所产生的收益一定比使较小的数减小的收益更加的大。
所以我们按ai从大到小排序,优先将m分配给最大的ai去减,如果分配后有相同高度的,那就得一起分配,这样才能保证最后的值最小。
最后的答案为分配完m后,
∑
i
=
1
n
(
a
i
m
)
2
\sum_{i = 1}^{n} (\frac{a_i} {m})^2
i=1∑n(mai)2
参考来源:
https://blog.nowcoder.net/n/ae8ba73294f14059bb74e25d09f04dcf
代码
这份代码在计算结果的时候为了避免分子上的除法导致精度问题,对分子分母都多乘了pos的平方,约分后答案依然正确,从而避免了精度问题。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e4 + 5;
bool cmp(ll a, ll b)
{
return a > b;
}
ll gcd(ll a, ll b)
{
return b == 0 ? a : gcd(b, a % b);
}
ll a[maxn];
ll pre_sum[maxn];
int main()
{
int n, m;
while(scanf("%d %d", &n, &m) != EOF)
{
for(int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
pre_sum[i] = 0;
}
sort(a + 1, a + 1 + n, cmp);
pre_sum[0] = -m;//前缀减去m后的值
for(int i = 1; i <= n; i++)
pre_sum[i] = pre_sum[i - 1] + a[i];
int pos = n;//假设够减
for(int i = 1; i <= n; i++)
{
if(pre_sum[i] > a[i + 1] * i)//如果前缀减去m后的高度,比后面一个高,说明已经停止。
{
pos = i;//一步减到i即可
break;
}
}
/*
注意这里没有分别考虑当pos = n的情况
当pos = n时,要除pos去计算,可能会有精度问题
这里直接再乘个pos,然后ai+1 到an都多乘pos平方
最后分母也多乘一个pos,约分后答案精确。
*/
ll ans_a = pre_sum[pos] * pre_sum[pos] * pos;//减掉后一样高的那几个,算他们的平方和
ll ans_b = pos * pos;
for(int i = pos + 1; i <= n; i++)
{
ans_a += a[i] * a[i] * ans_b;
}
ans_b *= m * m;
ll gcd_of_a_b = gcd(ans_a, ans_b);
ans_a /= gcd_of_a_b, ans_b /= gcd_of_a_b;
if(ans_b == 1 || (!ans_a))
printf("%lld\n", ans_a);
else
printf("%lld/%lld\n", ans_a, ans_b);
}
return 0;
}