一、几种常用的数制
1.十进制
(1)十进制计数的基数是10,每一位有0~9十个数码,逢十进一。
(2)任意一个十进制数均可展开为。
可以理解为对10的不同次幂的加权求和,可以通过对取余得到。
2.二进制
(1)每一位仅有0和1两个数码,逢二进一。
(2)任意一个二进制数均可展开为。
由此可以得出进制数展开式的普遍形式。
二、负数与小数的表示
1.负数的表示
以二进制为例,有符号的二进制表示,第一位为符号位,0表示证书,1表示负数;符号位之外,先将要表示的负数的绝对值表示出来,再对其取反(0变为1,1变为0)加一。
这样表示的目的是使得正数与负数可以用同样的运算符进行计算。
EG:7的二进制表示为0111,-7的二进制表示为1001,
7+(-7)=0, 0111+1111=0000。
2.小数的表示
小数部分的位数若为m位,则展开式中的i在小数部分的取值就为-1~-m,即等等,因此若十进制下小数的最后一位不为5,那么表示为二进制时,小数部分就是无限的,也因此 。
三、典型例题总结
1.有趣的二进制
题目来源
题目描述
小新在学C语言的时候,邝老师告诉他double类型的数据在表示小数的时候,小数点后的有效位是有限的,但是没有告诉他这是为什么,后来他发现0.1的二进制是一个无限循环小数0.000110011001100110011001100···,如果只取27位小数,再转换成十进制的话就变成了0.09999999403953552,小新开心的解决了这个问题。与此同时,小新又有了一个新的问题:一个数在64位二进制补码表示下,一共有多少个1。因为小数有无解的情况,所以我们保证输入的都是整数。
输入描述
有多组数据,每一行为一个数字n。
输出描述
输出这个数字在二进制补码下1的个数。
解题思路
暴力模拟。64位二进制补码需要采用unsigned long long来存,不断用读入的除以2取余数,余数依次存入初始清零的数组,最后遍历数组统计1的个数。代码时间复杂度。
通过代码
#include<bits/stdc++.h>
using namespace std;
int main(){
unsigned long long n;
while(cin>>n){
int a[64]={},i=0;
while(n){
a[i++] = n%2;
n /= 2;
}
int count = 0;
for(int j=0;j<64;j++){
if(a[j])
count++;
}
cout<<count<<endl;
}
return 0;
}
2.只能吃土豆的牛牛
题目来源
题目描述
旅行完了的牛牛又胖了,于是他终于下决心要戒掉零食,所以他带着他最爱的土豆回到了牛星,开始了在牛星种土豆和只吃土豆减肥的日子。(吃土豆能减肥么?)经过了辛勤的劳作,牛牛种的土豆奇迹般的收获了,于是他得到了很多很多很多很多的土豆(实在太多,数不过来了,你可以认为是无穷个)。他将这很多很多个土豆按照重量从小到大进行了排序,每个土豆的编号依次为1、2、3……N,然后他就惊奇地发现:由于牛星球的土壤很奇特,第i个土豆的重量正好是3^(i-1) 。 现在牛牛饿了要吃掉其中的若干个土豆。他每次拿的土豆的数目是任意的,选的土豆也是任意的。选中的土豆的总重量即每个土豆重量之和。例如:牛牛这一次拿了第一个土豆和第三个土豆,那么总重量为1+9=10。 牛牛想知道,在所有的选土豆方案里,他可以获得的第k大的“总重量”是多少。
输入描述
有多组输入样例。第一行是一个整数T,表示有T组测试样例,0 ≤ T ≤ 70。之后的T行中,每一行有一个数字k。(k<=2^31-1)
输出描述
针对每一个测试样例,输出一行;格式为: “Case #$Num: $A”,其中,$N表示第Num组样例,$A表示他可以获得的第k大的总重量。
解题思路
任意进制数的表示问题。本题中的各个土豆的重量即3进制表示中每一位的权重,第k大即将k用二进制表示后再将每一位乘以对应的3进制下的权重,求和即为第k大的总重量。
通过代码
#include<bits/stdc++.h>
using namespace std;
int main(){
int t;
cin>>t;
for(int i = 1;i <= t;i++){
int k,j = 0,a[32] = {};
cin>>k;
while(k){
a[j++] = k % 2;
k /= 2;
}
long long ans = 0;
for(int l = 0; l < j; l++){
if(a[l] == 0);
else
ans += pow(3,l);
}
printf("Case #%d: %lld\n",i,ans);
}
return 0;
}
3.进制转换
题目来源
链接:https://ac.nowcoder.com/acm/contest/19305/1028 来源:牛客网
题目描述
我们可以用这样的方式来表示一个十进制数: 将每个阿拉伯数字乘以一个以该数字所处位置的值减1为指数,以10为底数的幂之和的形式。例如:123可表示为 1*102+2*101+3*100这样的形式。
与之相似的,对二进制数来说,也可表示成每个二进制数码乘以一个以该数字所处位置的值-1为指数,以2为底数的幂之和的形式。一般说来,任何一个正整数R或一个负整数-R都可以被选来,作为一个数制系统的基数。如果是以R或-R为基数,则需要用到的数码为 0,1,....R-1。例如,当R=7时,所需用到的数码是0,1,2,3,4,5和6,这与其是R或-R无关。如果作为基数的数绝对值超过10,则为了表示这些数码,通常使用英文字母来表示那些大于9的数码。例如对16进制数来说,用A表示10,用B表示11,用C表示12,用D表示13,用E表示14,用F表示15。
在负进制数中是用-R 作为基数,例如-15(十进制)相当于110001(-2进制),并且它可以被表示为2的幂级数的和数:
110001=1*(-2)5+1*(-2)4+0*(-2)3+0*(-2)2+0*(-2)1 +1*(-2)0 设计一个程序,读入一个十进制数和一个负进制数的基数, 并将此十进制数转换为此负进制下的数:-R∈{-2,-3,-4,...,-20}
输入描述
第一个是十进制数N(-32768 ≤ N ≤ 32767); 第二个是负进制数的基数-R。
输出描述
输出此负进制数及其基数,若此基数超过10,则参照16进制的方式处理。
解题思路
基数为正数的情况与前题相同不再赘述。基数不为正数时有一些注意点:
一是基数为负数时需要特别注意。做除法时余数不能为负数,所以需要借位,借位时需要商的位置多加1,那么余数就会多加一个基数的绝对值(做除法时是被除数减去商乘以除数,向商多借1则是一个负数减去另一个更小的负数,才能得到正的余数),从而得到正的余数;
二是基数大于10时的输出表示,从10开始用字符依次表示,这里可以用一个字符串或者字符数组存好0到,计算时用一个数组存储每一位的结果,正向存储倒向输出,输出时直接输出(数组存余数,数组存字符);
三是弄清楚商和余数,基数是除数,是被除数,除以基数后是商,数组内存的是余数或加过基数的余数;
四是在用数组标号输出的情况下,为0的情况要单判,不然等号后进制表示法处会没有输出。
TMI
这题卡了很久,主要是一开始有点懵,退缩了,所以看了题解也没主动思考。搁置了几天再找了一些题解看,静下心捋顺了思路其实还是很基础的题。
写代码的时候卡了两个地方:
一个是计算时读入的后期直接用来计算,既是被除数也是商,所以在向商借位的时候是对进行++;
二是为了简化输出部分的代码,直接用的方式输出,这样做为0的时候是需要单独判断的,不然没有进入while循环,余数数组的数组容量一直为0,也就导致了输出时直接成了的形式,缺少了进制表示下的(实际上就是0),补上之后就通过了。
总的来说自己写代码卡住的部分确实也是看题解的时候没有参透的部分,这个题对进制换算的深入理解很有帮助。
通过代码
#include<bits/stdc++.h>
using namespace std;
int main(){
string str = "0123456789ABCDEFGHIJKLMNOPQR";
int n,r;
cin>>n>>r;
cout<<n<<"=";
int a[70] = {};
int i = 0;
if(n == 0)
cout<<"0";
else{
while(n != 0){
a[i++] = n % r;
n /= r;
if(a[i-1] < 0){
a[i-1] -= r;
n ++;
}
}
for(int j = i-1; j >= 0; j--)
cout<<str[a[j]];
}
cout<<"(base"<<r<<")"<<endl;
return 0;
}
尾声
虎年的第一天终于写完了第一篇博客,删删改改写了很久,有惰性导致的拖延,也有因为没能真正理解的东西不想糊弄着写的一丝真心。新一年要多多学习,多多做题,多多反思,多多总结。
虎年大吉,多福多乐!