zkc学长的福利

1.原题呈现

这里写图片描述

2.解决思路

根据题目的问题,最主要的问题在于求出所有同学所站顺序。通过分析我们可以发现一下几个问题。
(1)通过考虑左手的数字大小进行排序站队无法实现我们的要求。
(2)通过考虑右手的数字大小进行排序战队无法实现我们的要求。
(3)一组人员固定的情况下,则这组人员左手和右手的值是固定的。这种情况下,所有人左手数据乘积是固定的为M。那么对任意一个人所获得福利的大小最少为M/(R+L)。也就是M除以该用户左手的值和右手的值。因此为了使得所有人所获取福利数最小,我们将乘积最大的放在最后。

通过分析上述思路,我们整理得出以下方法
(1)输入数据,并对除了zkc学长外的所有成员进行排序
(2)排序的规则是,以左手和右手的数据乘积升序排列。
(3)从头开始依次计算每一个同学获取的福利(肉松饼个数)。
(4)输出最大值。

在上述的整个思路过程中,需要进行高精度运算。我们根据代码来分析面对数据量大的情况下如何进行高精度计算。

3.代码实现

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;

struct student{
    int leftvalue;
    int rightvalue;
}a[1000+5];

bool comp(const struct student& a, const struct student& b)
{
    if(a.rightvalue*a.leftvalue < b.leftvalue*b.rightvalue)
    {
        return true;
    }
    else
    {
        return false;
    }
}
int f[1000+5];
int fs[1000+5];
int main()
{
    int n;
    while(cin >> n)
    {
        for(int i = 0; i < n + 1; i++)
        {
            cin >> a[i].leftvalue >> a[i].rightvalue;
        }
        sort(a+1,a+n+1,comp);
        int number = 0;
        memset(f,0,sizeof(f));
        memset(fs,0,sizeof(fs));
        f[0] = a[0].leftvalue;
        long double t = a[0].leftvalue,s=0;
        int m = 0;
        for(int i = 1; i <= n; i++)
        {
            if(t/a[i].rightvalue > s)
            {
                s = t/a[i].rightvalue;
                m = 0;
                for(int j = 1000+4;j >= 0;j--)
                {
                    m = m*10000+f[j];
                    fs[j] = m/a[i].rightvalue;
                    m = m % a[i].rightvalue;
                }
            }
            t = t*a[i].leftvalue;
            int m = 0;
            for(int j = 0; j < 1000+5; j++)
            {
                m = m + f[j]*a[i].leftvalue;
                f[j] = m % 10000;
                m = m / 10000;
            }
        }
        m = 0;
        for(int i = 1000+4; i>=0; i--)
        {
            if(m == 1)
            {
                if(fs[i] < 10)
                {
                    cout <<"000";
                }
                else if(fs[i] < 100)
                {
                    cout <<"00";
                }
                else if(fs[i] < 1000)
                {
                    cout <<"0";
                }
                cout << fs[i];
            }
            else if(fs[i] > 0)
            {
                cout << fs[i];
                m = 1;
            }
        }
        cout << endl;
    }
    return 0;
}

这一段参考了zkc学长的福利做具体的高精度的实现。下面我们通过这段代码分析高精度的实现和我认为代码中存在的一些问题。

(1)使用高精度除法

for(int j = 1000+4;j >= 0;j--)
{
    m = m*10000+f[j];
    fs[j] = m/a[i].rightvalue;
    m = m % a[i].rightvalue;
}

在做除法运算时,我们需要从高位到低位除。因此在除法的计算过程中从数组尾部开始计算,数组尾部表示高位。

(2)使用高精度乘法

for(int j = 0; j < 1000+5; j++)
{
    m = m + f[j]*a[i].leftvalue;
    f[j] = m % 10000;
    m = m / 10000;
}

与高精度的除法相反,高精度的乘法需要从低位运算到高位,并进行进位运算。

(3)使用高精度输出

for(int i = 1000+4; i>=0; i--)
        {
            if(m == 1)
            {
                if(fs[i] < 10)
                {
                    cout <<"000";
                }
                else if(fs[i] < 100)
                {
                    cout <<"00";
                }
                else if(fs[i] < 1000)
                {
                    cout <<"0";
                }
                cout << fs[i];
            }
            else if(fs[i] > 0)
            {
                cout << fs[i];
                m = 1;
            }
        }

(4)存在的问题

<1>在乘积相同的情况下,先后顺序怎么考虑?

如K = ab = dc.且前面的数的积是t。如果将a b排在前面的话
第一个学生得到的肉松饼的个数是:t/b
第二个学生得到的肉松饼的个数是:t*a/d
同理如果将d c的学生排在前面的话,那么:
第一个学生得到的肉松饼的个数是: t/d
第二个学生得到的肉松饼的个数是: tc/b。
现在对每种情况下的每个学生的肉松饼个数都乘以K。
第一种情况下第一个学生获得肉松饼的个数是t*a/K,第二个学生获得肉松饼的个数是t*a*c/K
第二种情况下第一个学生获得肉松饼的个数是t*c/K,第二个学生获得肉松饼的个数是t*a*c/K
根据上诉情况可知在两种情况下,获得肉松饼的最大值都为t*a*c/K因此在左手和右手数目相同时,学生的排序与肉松饼最大值无关.

<2>在代码中通过long double的值存放左手数据的乘积是否会存在溢出?

long double 的取值范围为-1.2*10^-4932~1.2*10^4932,不会溢出。但是在不会溢出的情况下为什么不直接使用S强制转换成int型数据呢?
个人猜测,在数据量较大的情况下例如1000 个999 1结果如下所示:
这里写图片描述
在这种情况下使用long double来表示精确度不够,可能存在较大的误差,但是在题中使用long double是只是用来比较没有进行精确的运算。

4.遇到的问题

(1)没有使用高精确度来解题。
(2)在参考高精确度的使用时也出现了一系列的小问题。

5.相关链接

zkc学长的福利(NYOJ)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值