计蒜客题解——最大的余数

其实本题的难度真心不高,但是可以完整说明数据分析、标程、随机数生成、对拍等部分。

题目链接

原题来自计蒜客的某次比赛。计蒜客对应的链接为https://nanti.jisuanke.com/t/42227

或者我自己OJ的链接为http://47.110.135.197/problem.php?id=5150

题面

给定一个正整数 n,请找出一个不大于 n 的正整数 p,使得 n 除以 p 的余数最大,并求出这个最大的余数。

输入

只有一行,包含一个正整数 n。

输出

只有一行,包含你的答案。

样例输入

5

样例输出

2

数据范围

一共 20 个测试数据
对于前 30% 的数据,1 ≤ n ≤10。
对于前 60% 的数据,1 ≤ n ≤10^6。
对于前 90% 的数据,1 ≤ n ≤ 10^18。
对于前 100% 的数据,1 ≤ n ≤ 10^1000。

题目分析

解题思路

求余数,而且要求余数最大。哪么必然意味着除数要最小,哪么除数必然为 1。也就是说,这题就是一个 2 的余数问题。进一步分析,我们可以知道如果被除数为奇数,哪么这个最大的余数为 n/2;如果被除数为偶数,哪么这个最大的余数为 n/2 - 1。等效于(n-1)/2。

数据范围分析

30%的数据,1 ≤ n ≤10。可以用int可以表示 n。

60% 的数据,1 ≤ n ≤10^6。也可以用int可以表示 n。

90% 的数据,1 ≤ n ≤ 10^18。只能用unsigned long long来表示 n。

100% 的数据,1 ≤ n ≤ 10^1000。WTF,已经超过了unsigned long long的范围了,需要用到高精度表示。

代码

90%通过代码

没有学过高精度怎么办?只能拿自己可以拿到的分数,也就是使用unsigned long long可以拿到90%的分数。对应代码如下:

#include <iostream>

int main() {
    unsigned long long n;
    std::cin >> n;
    std::cout << (n-1)/2 << std::endl;
    return 0;
}

100%通过代码

如果要拿到满分必须使用高精度的除法。对应代码如下:

//5150
//http://47.110.135.197/problem.php?id=5150
#include <cstdio>
#include <cstring>

const int MAXN = 2000;
char str[MAXN];
int data[MAXN] = {};
int minus[MAXN] = {1};

int main() {
    //freopen("1.in", "r", stdin);
    //freopen("1.out", "w", stdout);
    //读入数据
    scanf("%s", str);

    //反过来
    int len = strlen(str);
    int i;
    for (i=0; i<len; i++) {
        data[i] = str[len-i-1] - '0';
    }

    //减去1
    for (i=0; i<len; i++) {
        if (data[i] >= minus[i]) {
            data[i] -= minus[i];
            break;
        } else {
            data[i+1]--;
            data[i] = 10+data[i]-minus[i];
        }
    }

    //除以2
    for (i=len-1; i>=0; i--) {
        if (1==data[i]) {
            data[i-1] += data[i]*10;
            data[i] = 0;
        } else if (1==data[i]%2) {
            //有余数
            data[i] /= 2;
            if (i>0) {
                data[i-1] += 10;
            }
        } else {
            data[i] /= 2;
        }
    }

    //删除后导零
    for (i=len-1; i>=0; i--) {
        if (0!=data[i]) {
            break;
        }
        len--;
    }

    //输出
    for (i=len-1; i>=0; i--) {
        printf("%d", data[i]);
    }
    if (0 == len) {
        printf("0");
    }
    printf("\n");

    //fclose(stdin);
    //fclose(stdout);
    
    return 0;
}

对拍

怎么验证自己写的高精度代码是正确的,很简单对拍啊。说实话,我自己也是通过对拍修改了好几次代码才通过的。

我们使用90%通过代码作为标准程序和高精度代码进行对拍。虽然只能有90%的数据集通过测试,这样对拍可以保证高精度代码也是正确的。因此我们首先需要写一个随机数据生成cpp。

随机数据生成

由于标准程序最大的数据范围是 unsigned long long,也就是18446744073709551615,对应为1e19。所以要注意产生的数据不能操作1.845e19这个大小。下面是参考的随机数生成代码。

//生成数据 5150
#include <cstdio>
#include <cstdlib>
#include <ctime>

int main(int argc, char *argv[]) {
    int seed = time(NULL);
    if (argc>1) {
        //有参数
        seed = atoi(argv[1]);
    }
    srand(seed);

    //生成长度
    const int lenMax = 19;//1000位
    const int lenMin = 1;
    int len = lenMin + rand()%(lenMax-lenMin+1);

    int i;
    for (i=0; i<len; i++) {
        //输出0到9
        int lo = 0;
        int hi = 9;
        int data = lo + rand()%(hi-lo+1);
        if (0==i && 0==data) {
            data = 1 + rand()%(hi);
        }

        printf("%d", data);
    }
    printf("\n");

    return 0;
}

Win下对拍的BAT文件

@echo off
set cnt = 1
:loop
	echo ==第%cnt%次测试
	set /a cnt = %cnt% + 1
	gen.exe %random% > data.in
	std.exe < data.in >std.out
	5150.exe < data.in > my.out
	fc my.out std.out
if not errorlevel 1 goto loop
pause
goto loop

对拍的过程

生成了对应的gen.exe、std.exe和5150.exe后,我们执行对应的bat文件即可。下面是对拍的结果截图。

已经对拍了5677次,标准程序和高精度程序的结果都是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的老周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值