文章目录
- 前言
- 一、题目
- 二、暴力解题步骤(50分)
- 三、打表防坑解题(100分)
- 总结
前言
受不了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用的。
总结
这题说有多难嘛,还真算不上,不过代码是真的长啊~ 而且坑也是比较大的,其实在笔者想来这种题真是纯为了竞赛出的,实际意义也是真的不大。