目录
[1. 进制的本质](#1. 进制的本质)
[2. 任意进制转换为十进制](#2. 任意进制转换为十进制)
[3. 十进制转换为任意进制](#3. 十进制转换为任意进制)
[4. 例题](#4. 例题)
在蓝桥杯以及各种编程竞赛中,进制转换是一个非常经典且高频考察的基础知识点。无论是单独作为一道填空题出现,还是作为复杂大题中的一个小步骤,熟练掌握进制转换的底层逻辑和代码模板,都是拿到高分的重要保障。
今天,我们就来拨开进制的迷雾,彻底搞懂进制转换的核心方法,并结合三道蓝桥杯真题进行实战演练。
1. 进制的本质
什么是进制?通俗地讲,进制就是一种计数的规则。我们平时在生活中最常用的是"十进制",它的核心规则就是"逢十进一"。每一位上的数字只能是 0 到 9,当某一位加到 10 的时候,就要向前一位进 1。
同理,"二进制"就是逢二进一(数字只有 0 和 1),"十六进制"就是逢十六进一(数字除了 0 到 9,还引入了 A 到 F 来表示 10 到 15)。
进制的另一个核心概念是"位权"。以十进制数字 345 为例,它其实代表的是 3 乘以 100,加上 4 乘以 10,再加上 5 乘以 1。这里面的 100、10、1,就是各个位置上的"权重"。任何进制的本质,都可以看作是各个位上的数字乘以对应位置的权重,然后全部相加。
2. 任意进制转换为十进制
掌握了进制的本质,我们就很容易理解如何把一个任意的 k 进制数转换成十进制了。
最直接的方法是从左到右(从高位到低位)遍历这个数的每一位,每次都把当前累积的结果乘以 k,再加上当前位上的数字。这种方法在算法中类似于"秦九韶算法",它的好处是不需要去计算权重的多少次方,只需要用一个循环就能轻松搞定。
核心代码模板片段:
ll x = 0;
for(int i = 1; i <= n; i++)
{
x = x * k + a[i];
}
cout << x << '\n';
3. 十进制转换为任意进制
把十进制转换为任意的 k 进制,我们通常使用"除 k 取余法"。
具体操作是:把十进制数不断地除以 k,每次除法得到的"余数"就是目标 k 进制数的对应位上的数字。我们一直除,直到原来的数字变成 0 为止。
需要特别注意的是,我们最先求出来的余数,实际上是 k 进制数的最末尾(也就是最低位)。所以当我们把所有余数依次存入数组或字符串后,还需要将其整体"翻转"一下,才能得到正确的、高位在前的结果。
核心代码模板片段:
ll x;
cin >> x;
while(x) a[++cnt] = x % k, x /= k;
reverse(a + 1, a + 1 + cnt); // 翻转,使高位在1的位置
4. 例题
下面我们结合三道蓝桥杯真题,来看看这两套模板在实战中是如何应用的。
4.1进制
https://www.lanqiao.cn/problems/2489/learning/?page=1&first_category_id=1&name=%E8%BF%9B%E5%88%B6
思路解析: 这是一道纯粹的十六进制转十进制的填空题。给定的十六进制字符串是 "2021ABCD"。 我们可以完全套用"任意进制转十进制"的模板。首先把字符串里的字符转换成对应的数字存入数组,注意 A 到 F 需要特殊处理转换为 10 到 15。然后再做累乘求和。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 50;
int a[N];
int main()
{
string s = "2021ABCD";
for(int i = 0; i < s.length(); i++)
{
if('0' <= s[i] && s[i] <= '9') a[i + 1] = s[i] - '0';
else a[i + 1] = s[i] - 'A' + 10;
}
ll x = 0;
for(int i = 1; i <= s.length(); i++)
{
x = x * 16 + a[i];
}
cout << x << '\n';
return 0;
}
第二种极简解法代码: 因为十六进制是非常常用的计算机底层进制,C++ 语言本身就原生支持直接读取和输出。只要在数字前面加上 0x 前缀,编译器就会自动把它当成十六进制,并在输出时默认转成十进制打印。
#include <bits/stdc++.h>
using namespace std;
int main()
{
unsigned int x = 0x2021ABCD;
cout << x << '\n';
return 0;
}
4.2九进制转十进制
https://www.lanqiao.cn/problems/2095/learning/?page=1&first_category_id=1&problem_id=2095
思路解析: 这题是把九进制的 "2022" 转成十进制。和上一题思路一模一样,只是把乘数 16 换成了 9。因为只有数字没有字母,转换起来更方便。
第一种数组写法代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 10;
int a[N];
int main()
{
string s = "2022";
for(int i = 1; i <= s.length(); i++)
{
a[i] = s[i - 1] - '0';
}
ll x = 0;
for(int i = 1; i <= s.length(); i++)
{
x = x * 9 + a[i];
}
cout << x << '\n';
return 0;
}
第二种精简循环写法代码: 为了让代码更优雅,我们甚至不需要借助额外的数组,直接在遍历字符串字符的同时,一边转换数字一边累加。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
ll x = 0;
string s = "2022";
for(char c : s)
{
x = x * 9 + (c - '0');
}
cout << x << '\n';
return 0;
}
4.3进制转换
https://www.lanqiao.cn/problems/1230/learning/?page=1&first_category_id=1&problem_id=1230
思路解析: 这是一道非常综合的进制转换题,要求把一个 N 进制的字符串转换为 M 进制的字符串输出。 解题核心思路是把"十进制"作为一座桥梁:先用模板 1 把 N 进制字符串转成十进制整数,然后再用模板 2 把这个十进制整数转换为 M 进制的字符串。 在代码实现上,我们把大任务拆分成了几个功能单一的小函数,这样逻辑非常清晰,也不容易出错。
参考代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 字符转数字
inline int char2num(char c)
{
if(isdigit(c)) return c - '0';
return c - 'A' + 10;
}
// 数字转字符
inline char num2char(int x)
{
if(x < 10) return x + '0';
return x - 10 + 'A';
}
// N进制字符串 -> 十进制整数
ll str2dec(const string &s, int n)
{
ll num = 0;
for(char c : s)
{
num = num * n + char2num(c);
}
return num;
}
// 十进制整数 -> M进制字符串
string dec2str(ll num, int m)
{
if(num == 0) return "0";
string res;
while(num)
{
res += num2char(num % m);
num /= m;
}
reverse(res.begin(), res.end());
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while(T--)
{
int N, M;
string s;
cin >> N >> M >> s;
ll dec = str2dec(s, N);
cout << dec2str(dec, M) << '\n';
}
return 0;
}
本章完。