原题:https://www.luogu.com.cn/problem/P1017
(不再粘贴题目了,太麻烦)
P1017 进制转换 【普及/提高-】 题解
第一部分:痛苦与绝望
经过了两个小时的煎熬,这道题基本可以算是将近把我逼疯了。看着其他同学的五一假期作业分数早已经突破800分,【提高+/省选-】的题一遍就过,我却还在400分左右徘徊,这样一道黄题都过不了......
废话不多说,进入正题
这是我耗了两个小时的成果:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<vector> using namespace std; vector<int> a; int n,R,len; pair<int,int> range[100]; char vir[21]="0123456789ABCDEFGHIJ"; int main() { cin>>n>>R; range[0].first=0; range[0].second=-R; for(int i=0; pow(-R,i)<=2147483647; i++) { if(i%2==1) { range[i].first=range[i-1].first-pow(-R,i); range[i].second=range[i-1].second; } else if(i%2==0) { range[i].first=range[i-1].first; range[i].second=range[i-1].second+pow(-R,i); } if(n<=range[i].second&&n>=range[i].first) { len=i; break; } } // cout<<len<<endl; cout<<n<<"="; for(int i=len+1; i>=1; i--) { if((i-1)%2==0&&n>0) { for(int j=0;; j++) { if(j*pow(-R,i-1)>=n||j==-R-1) { //cout<<pow(-R,i-1)<<endl<<n<<endl; cout<<vir[j]; n -= j*pow(-R,i-1); break; } } } else if((i-1)%2==1&&n<0) { for(int j=0;; j++) { if(-(j)*pow(-R,i-1)<=n||j==-R-1) { // cout<<-pow(-R,i-1)<<endl<<n<<endl; cout<<vir[j]; n += j*pow(-R,i-1); break; } } } else { cout<<0; continue; } } cout<<"(base"<<R<<")"<<endl; return 0; }
然而命运多舛,如果给定的数是负数,这个代码就无能为力了。我将近哭了出来,心想难道这就是智力不够吗......就是因为它,还有其他同学遥遥领先的进度,我在网课上将近崩溃,不得不请假离开......
第二部分:清醒与悔悟
痛定思痛,结合题解,我认真地反思了一下。
把十进制转换成其他进制,最基础的方法是什么?
不就是用原十进制数一步步除以要转换的进制数,将余数倒序输出么!这题虽然有负数,但思路一定是同样的,只需要做出一些微调就行!
然而,从一开始,我的思路就错了。我先从“大”处理,就是先算出转换后的位数,然后一步步调用pow(求幂函数),一开始就会出现很大的数字,然后再求值、取余......殊不知这已经远远偏离了正确思路。
在数次痛苦的哀嚎后,我才明白,这是无济于事的。我需要解决问题。
经过一次次心理的狂风巨浪以后,我的思路正在向真理接近......
第三部分:希望与涅槃
以下是正确的思路。
对于非负数进制的转换(原数是十进制),设要转换成 R 进制,只要每次将原数除以 R ,得到一系列余数 m1 , m2 , m3 ... mn ,然后再按照逆着的顺序输出 mn ,mn-1 , ... , m3 , m2 , m1。
也就是说,我们只需要取余,从原数减去 余数乘以进制数 R 的积 ,然后继续往下寻找(递归)就行。
//版本1 void turn(int n,int r)//n是当前需要模的数字,r是模数 { if(n==0) return; int m=n%r; 存储m; turn(n/r,r);//下一步 } int main() { 输入n,r; turn(n,r); 倒序输出所有存储的m; } //版本2 void turn(int n,int r) { if(n==0)return; int m=n%r; turn(n/r,r); cout<<m;//在递归后输出,能保证倒序 }
但是对于这道题,很明显其中会出现负数。对于被模数是负数的情况,计算机会返回一个负数,例如:-5 % -3 = -2 , -5 % 3 = -2。
负数余数是我们不想要的情况,需要把它转换成正数。
经过很长时间的探究后,我发现了规律:如果出现负数余数,那么就把它减去负进制数 R (R < 0),这样会使它成为正数,然后进行下一步递归时,将 n 除以 r 所得的商加上 1 :
m-=r; turn(n/r+1,r);
然后再根据正数情况处理就行了。
我的代码运用STL模板,具有很高的整合性。我的想法:单独地编写一个取余函数,能够处理负数余数。然后返回一个pair,一个存储得到的余数,一个存储一个bool值,表示它是不是遇到了负数的情况,也就是商需不需要加一。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<stack> using namespace std; char vir[21]="0123456789ABCDEFGHIJ"; int N,R; stack<int> st;//栈具有后进先出的性质,适合倒序输出 pair<int,bool> ultra_mod(int n,int r) { int mod=n%r; pair<int,bool> res; if(mod<0) { mod-=r;//处理负余数 res.second=true;//有负余数,需要在下一步递归时+1 } else res.second=false;//不需要+1 res.first=mod;//导出余数 return res; } void turn(int n,int r) { if(n==0) return;//停止递归 pair<int,bool> mod=ultra_mod(n,r);//获得余数信息 st.push(mod.first);//将余数压进栈 if(mod.second == true) turn(n/r+1,r);//遇到负数情况 else turn(n/r,r); } int main() { cin>>N>>R; turn(N,R); cout<<N<<"="; while(!st.empty()) { cout<<vir[st.top()];//倒序输出所有余数(若大于9先转换成字母) st.pop(); } cout<<"(base"<<R<<")"; return 0; }