19年牛客多校第一场C题 Euclidean Distance(拉格朗日乘子法 或 小思维题)

这道题的原题意给出了约束条件,要求函数极值。
这里可以用大一下册的拉格朗日乘子法的知识。过程比较复杂,这里贴一个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=1n(aipi)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=1n(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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值