C++详解NOI题:[NOIP2021] 报数


前言

受不了CSDN每日一练的在线竞赛系统了,bug多就算了,勉强能用,可那些题目的神描述,到处是错。所以找点别的题来玩,看到一道NOI的题挺有意思,就试着解解。
在这里插入图片描述


提示:以下是本篇文章正文内容,下面案例可供参考

一、题目

题目描述:
报数游戏是一个广为流传的休闲小游戏。参加游戏的每个人要按一定顺序轮流报数,但如果下一个报的数是 7 的倍数,或十进制表示中含有数字 7,就必须跳过这个数,否则就输掉了游戏。

在一个风和日丽的下午,刚刚结束 SPC20nn 比赛的小 r 和小 z 闲得无聊玩起了这个报数游戏。但在只有两个人玩的情况下计算起来还是比较容易的,因此他们玩了很久也没分出胜负。此时小 z 灵光一闪,决定把这个游戏加强:任何一个十进制中含有数字 7 的数,它的所有倍数都不能报出来!

形式化地,设 p(x) 表示 x 的十进制表示中是否含有数字 7,若含有则 p(x)=1,否则 p(x)=0。则一个正整数 x 不能被报出,当且仅当存在正整数 y 和 z ,使得 x=yz 且 p(y)=1。

例如,如果小 r 报出了 6 ,由于 7 不能报,所以小 z 下一个需要报 8;如果小 r 报出了 33,则由于 34=17×2,35=7×5 都不能报,小 z 下一个需要报出 36 ;如果小 r 报出了 69,由于 70∼79 的数都含有 7,小 z 下一个需要报出 80 才行。

现在小 r 的上一个数报出了 x,小 z 想快速算出他下一个数要报多少,不过他很快就发现这个游戏可比原版的游戏难算多了,于是他需要你的帮助。当然,如果小 r 报出的 x 本身是不能报出的,你也要快速反应过来小 r 输了才行。

由于小 r 和小 z 玩了很长时间游戏,你也需要回答小 z 的很多个问题。

输入格式,最大数T <= 2x105, x <= 107
第一行,一个正整数 T 表示小 z 询问的数量。

接下来 T 行,每行一个正整数 x,表示这一次小 r 报出的数。

输出格式
输出共 T 行,每行一个整数,如果小 r 这一次报出的数是不能报出的,输出 −1,否则输出小 z 下一次报出的数是多少。

示例 输入: 4 6 33 69 300 输出:8 36 80 -1

二、暴力解题步骤(50分)

一看这种题,第一个想法肯定是暴力先来一波嘛~,说干就干!嗯,这个平台是用的文件输入输出的形式解题的,就是说得自己写从文件读数据,然后计算完成后自己写入一个文件当交作业。也是有点折腾人的哈,因为笔者这代码结合在一起了,懒得拆了,一起贴上。

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

bool check(int num){
    bool res;
    string sn = to_string(num);
    int indx = sn.find('7');
    if(indx != string::npos) {
        return false;
    }else res = true;   
    return res;
}

bool resolve(int num){
    bool res;
    for (int i=1; i<num/2+1; ++i){
        if(num%i==0){
            if (check(i) && check(num/i)) {
                res = true;
            }else{
                res = false;
                break;
            }
        }
    }  
    return res;
}

int main(){

    vector<int> vec;
    ifstream fin;
    fin.open("number.in", ios::in);
    if(fin.is_open()){
        string buf;
        while (getline(fin, buf)){
            vec.push_back(stoi(buf));
        }
        fin.close();
    } 
    int n = vec[0];
    vec.erase(vec.begin());
    
    int tmp;
    ofstream fout;
    fout.open("number.out");
    for (int val :vec){
        //cout << "in: " << val << endl; 
        if (resolve(val)){
            tmp = val;
            while(tmp){
                tmp++;
                if(resolve(tmp)){
                    break;
                }else continue;
            }
        }else{
            tmp = -1;
        }
        fout << tmp << endl;
        fout.flush();
    }
    return 0;
}

反正这平台啥都得自己写,费事很。所以这题的暴力解法,费了我好大劲的…其实main函数中那么一大串都是在读文件,写文件。有用的就是for循环把数据送给resolve处理,这个函数是为了分解出因数,分解后交给check函数检测是否有7,有就中止,没有就继续。其实这也做了一些小优化的,不然50分都不一定有,比如将分解因数上限取为一半,除数和商都是因数,一起检测什么的。最后回复给main函数。一个个问题测试一个个写入输出文件就搞定了,本机测试很好嘛~ 拿到平台上一测,hehe!too young too simple 了,50分。很显然是有特大数据要处理的,这种笨法子能过,那noi就没含金量了。那就只能改了,本着以空间换时间的思想,咱先把所有含7的数算出来,放到数组对应的下标中。问题文件中要啥咱就去查一下不就成了。换打表思想来做了试试,回想一下小学数学就开干:

三、打表防坑解题(100分)

很显然我这maximum的值略小了一个数量级,笔者这老苹果MAC不给分这么多连续内存… 这种题的这种解法也是让人无语的,这比赛真有这么多内存给用吗?为了验证一下,笔者换 了一台win电脑,嗯~ 那个电脑内存够大,确实没有问题,通过计算108也就1千万(题目描述是107,实际是8次方,C++中1e8是一千万),bool值才1个字节,算起来也就十M左右。

bool* finded(){
    int const maximum = 1000009;  //中间少了个0
    bool table[maximum];
    for (int i=0; i< maximum; ++i){
        table[i] = 1;
    }
    int tmp;
    for (int i=7; i<maximum; ++i){   // 求出所有含7的数的倍数
        tmp = i;
        while (tmp){
            if (tmp%10==7){
                int j = 1;
                while(i*j<maximum){
                    //cout << i*j << " ";
                    table[i*j] = 0;
                    j++;
                }
            }
            tmp /= 10;
        }
    }
    bool* ptr = table;
    return ptr;
}

int main(){   
    bool *table = finded();
    vector<int> vec;
    ifstream fin;
    fin.open("number.in", ios::in);
    if(fin.is_open()){
        string buf;
        while (getline(fin, buf)){
            vec.push_back(stoi(buf));
        }
        fin.close();
    } 
    int n = vec[0];
    vec.erase(vec.begin());
    
    int tmp;
    ofstream fout;
    fout.open("number.out");

    for (int val :vec){
        std::cout << "in= " << val << endl;
        if (table[val]){
            tmp = val+1;
            while(tmp){
                string sn = to_string(tmp);   // 防6999999
                int ind = sn.find("7");
                int sl = sn.length();
                if(ind != string::npos && sl-ind >1){
                    tmp = tmp + pow(10, sl-1-ind) - stoi(sn.substr(ind+1, sl));
                }           
                if (table[tmp]){
                    break;  
                }else tmp++;               
            }
        }else{
            tmp = -1;
        }     
        fout << tmp << endl;
        fout.flush();
    }
    return 0;
}

总体思路也不复杂,首先建了一个bool数组,将含7的数的倍数都做为下标,表示为false。其余不含7的表示为true。如此只需在回答问题前查一下数组即可知道是否可以回答,同理问题也要先检查,如果在表中表示为false的回答-1。

为了避免6999999这种特大坑,笔者想了个办法:即将回答转成字符串,tmp = tmp + pow(10, sl-1-ind) - stoi(sn.substr(ind+1, sl)); 这里的判断比较复杂,但这是笔者想到的最好办法了!只有7在十位以上才直接加上10的n次方,一次性跳过70,700,7000…,这种类型的数字,以免不停查数组。

顺便说一下,在某平台编译不了。应该是bool数组的指针问题,好像不让这么写。才吐槽完CSDN的在线编译器烂,这还有更烂的!我改成直接copy数组,不报段错误了。又给我说table[val]的下标无效。笔者把那个finded函数去了,也写到main里面就好了,也就不用考虑指针传递的事情了。测试数据肯定是没问题的,但是…输入数据只过了70,说是中间又出了段错误!想了想,是笔者犯傻了,忘记把maximum值改大了,确实会段错误,查表的时候超出范围了嘛,再加上个零后。完美通过!

如果抄作业的话,改进某平台的编译器,把后一个tmp变量写到for里面去。这里的写法在vscode中编译通过,也没有warning,小时候老师教导过:一个函数就干一件事!后面的解题代码没有写头文件,和暴力解法比要多个cmath引入,是计算pow用的。


总结

这题说有多难嘛,还真算不上,不过代码是真的长啊~ 而且坑也是比较大的,其实在笔者想来这种题真是纯为了竞赛出的,实际意义也是真的不大。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

无证的攻城狮

如本文对您有用,大爷给打个赏!

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

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

打赏作者

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

抵扣说明:

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

余额充值