例26   二进制数中1的个数

问题描述

如果一个正整数m表示成二进制,它的位数为n(不包含前导0),称它为一个n位二进制数。所有的n位二进制数中,1的总个数是多少呢?

例如,3位二进制数总共有4个,分别是4(100)、5(101)、6(110)、7(111),它们中1的个数一共是1+2+2+3=8,所以所有3位二进制数中,1的总个数为8。

输入格式

一个整数T,表示输入数据的组数,接下来有T行,每行包含一个正整数 n(1<=n<=20)。

输出格式

对于每个n ,在一行内输出n位二进制数中1的总个数。

输入样例

3

1

2

3

输出样例

1

3

8

(1)编程思路1。

对于输入的n,n位二进制数m是位数为n并且首位为1的二进制数,且满足:

    2n-1 ≤ n位二进制数m  <  2n

  因为首位为1,n位二进制数的个数就是n-1位的0和1的组合数,即2n-1个。

  第1位必须为1,所以第1位的1的个数为2n-1个。

  其他n-1位,总位数为(n-1)* 2n-1。其中0和1的个数是一半对一半,所以1的个数为(n-1)* 2n-1/2。

  合计1的位数为:2n-1 +(n-1)* 2n-1/2。

因此,n位二进制数中1的个数直接用上式计算出来。计算时,用移位运算来计算2的n次方是一种快速的计算方法。

即n位二进制数中1的个数为 :1<<(n-1)+(n-1)*(1<<(n-2))。

(2)源程序1。

#include <stdio.h>

int main()

{

int t,n;

scanf("%d",&t);

while(t--)

{

scanf("%d",&n);

int ans=(1<<(n-1))+(n-1)*(1<<(n-2));

printf("%d\n",ans);

}

return 0;

}

(3)编程思路2。

设数组元素f[i]的值表示i位二进制数中1的个数。因为i位二进制数可以看成是i-1位二进制数的每个数在其最右边分别加上1或0得到的。因此,i位二进制数中1的个数一定是i-1位二进制数中1的个数的二倍,再加上i-1位二进制数的个数(因为每个数最右边如果加上1,1的个数会增加1个,加上0不会增加)。

即  f[i]=2*f[i-1]+2i-2

初始时,f[1]=1,  f[2]=2*f[1]+2^0=3 。

(4)源程序2。

#include <stdio.h>

int main()

{

int f[21]={0,1};

for(int i=2;i<=20;i++)

{

f[i]=2*f[i-1]+(1<<(i-2));

}

int t,n;

scanf("%d",&t);

while(t--)

{

scanf("%d",&n);

printf("%d\n",f[n]);

}

return 0;

}

习题26

26-1  1的个数相同

问题描述

给定一个大于0的整数n,把它转换为二进制,则其二进制数中至少有1位是“1”。编写一个程序,找出比给定的整数n大的最小整数m。要求m和n两个整数转换成二进制数后,二进制数中包含的1的个数相同。

例如,120的二进制数为01111000,则比120大且二进制数中1的个数相同的最小整数为135(10000111)。

输入格式

输入包含若干组数据。每组数据是一个整数 N (1<=N <=65535)。N = 0 时输入结束。

输出格式

对于每组数据,在单独的一行输出一个整数m。

样例输入

92

120

0

样例输出

99

135

(1)编程思路1。

寻找比n大的最小的整数m,最容易想到的方法是从n+1开始穷举。首先把十进制整数n转化为二进制,然后穷举比这个十进制整数大的数m,判断m和n两个数对应的二进制数中1的个数是否相同。判断的方法就是,把十进制数用n&1的位运算依次取出末位然后全部加起来,若两个数的所有二进制位加起来相等,则这两个数的二进制位一定有相同个1。

(2)源程序1。

#include <stdio.h>

int main()

{

int n,a,b,d,m;

while (scanf("%d",&n) && n!=0)

{

a=n;  b=0;

while(a)

{

b+=a&1;    a>>=1;

}

m=n;

do {

d=0;  m++;   a=m;

while(a)

{

d+=a&1;   a>>=1;

}

} while(d!=b);

printf("%d\n",m);

}

return 0;

}

(3)编程思路2。

对十进制数n转化成的二进制数直接进行位变换,求出最小的整数m。

具体方法是:先找到整数n对应的二进制数的最右边的1个1,从这个1开始,从右向左将连续出现的k个1变为0后,高1位的0变为1,再从最低位开始,将k-1个0变为1,即可得到最小的数n。

例如,32 对应的二进制数为00100000,将最右边的连续1个1变为0,高1位0变为1,即为01000000,对应整数为64。

又如,92 对应的二进制数为 01011100,将最右边的连续3个1变为0(得01000000),高1位变为1(得01100000),再将最低位的2(3-1)个0变为1,即为01100011,对应整数为99。

(4)源程序2。

#include <stdio.h>

int main()

{

int n,a,b,k,m;

while (scanf("%d",&n) && n!=0)

{

for (a=0; (n & (1<<a))==0; a++) ;    // 找到最右边的1个1所在位置a

for (b=a; (n & (1<<b))!=0; b++) ;    // 找到从a位开始向左的连续个1

m =n | (1<<b);                   // 把b位改成1

for (k=a; k<b; k++)  m^=(1<<k);   // 将从a位到b-1位的1全部取反变为0

for (k=0; k<b-a-1; k++) m |= 1<<k;  // 将最低的b-a-1个位的0变为1

printf("%d\n",m);

}

return 0;

}

(5)编程思路3。

仔细琢磨整数的补码表示和位运算,可以将上面程序中的几个循环用一个表达式来完成。

1)按补码的表示法,正数的补码与原码相同,负数的补码是相应正数的补码的各位取反后加1。例如,以8位为例,32的补码是00100000,-32的补码是11100000;又如,92的补码是 01011100,-92的补码是 10100100。可以看出,把绝对值相等的正负两个整数用二进制数补码表示出来,从最低位开始到第1次出现1的地方为止,两者是一致的,高位部分的0和1恰好是相反的。利用这个特性,将正数m和相应的负数 –m进行逻辑与(&)的话,就能得到最初1出现的地方。

设x是整数n的二进制数保留最右边一个1,其余各位变为0后,所得到的数,则x = n&(-n)。

例如,n=92(01011100),则 -n=-92(10100100),x = n&(-n)  = 01011100&10100100 = 00000100。

2)n+x 是从右往左将整数n的第一个01转化为10。这是因为从最右边的一个1到第一个01,之间必然全是1,加上x后会一直进位,直到把01变为10,此时10的右边必然全是0。

例如,n=92,则 n+x=01011100 + 00000100=01100000。

3)表达式 n^(n+x) 可将整数n中最右边的第1个1开始,连续出现的1保留下来,且第1个01转化成的10中的1也保留下来,其余位全部为0。 n/x可以去掉最右边的所有0。

例如,n=92,n^(n+x) = 01011100^01100000 = 00111100。

n^(n+x)/x = 00111100/00000100 =00001111。

即 n^(n+x)/x 相当将k+1(k为从整数n的最右边的1个1开始,从右向左连续出现的1的个数)个1全部右移到最右边,且左边全部清0。由于最右边只需将k-1个0变为1,因此,将n^(n+x)/x /4可以右移两位,去掉两个1。

4)n+x+(n^(n+x))/x/4就是所求的最小整数。

(6)源程序3。

#include <stdio.h>

int main()

{

int n,x,m;

while (scanf("%d",&n) && n!=0)

{

x=n&-n;

m= n+x+(n^(n+x))/x/4;

printf("%d\n",m);

}

return 0;

}

26-2  二进制

本题选自洛谷题库 (https://www.luogu.org/problem/P2104)

题目描述

小Z最近学会了二进制数,他觉得太小的二进制数太没意思,于是他想对一个巨大二进制数做以下 4 种基础运算:

运算 1:将整个二进制数加 1

运算 2:将整个二进制数减 1

运算 3:将整个二进制数乘 2

运算 4:将整个二进制数整除 2

小Z很想知道运算后的结果,他只好向你求助。

(Ps:为了简化问题,数据保证+,-操作不会导致最高位的进位与退位)

输入格式

第一行两个正整数 n,m,表示原二进制数的长度以及运算数。

接下来一行 n 个字符,分别为‘0’或‘1’表示这个二进制数。

第三行 m 个字符,分别为‘+’,‘-’,‘*’,‘/’,对应运算 1,2,3,4。

输出格式

一行若干个字符,表示经过运算后的二进制数。

输入样例

4 10

1101

*/-*-*-/*/

输出样例

10110

(1)编程思路。

由于数据保证+,-操作不会导致最高位的进位与退位,因此直接根据运算符进行模拟运算即可。各算符的模拟运算方法分别为:

1)“+”: 从最后一个数(串中元素num[n-1])开始向前搜索,直到遇到“0”为止,中途所遇到的每个字符“1”都变成字符“0”(相当于二进制数+1,且进位),最后遇到的“0”变成“1”。

2)“-”: 从最后一个数(串中元素num[n-1])开始向前搜索,直到遇到“1”为止,中途所遇到的每个字符“0”都变成字符“1”(相当于二进制数-1,且向前借位),最后遇到的“1”变成“0”。

3)“*”:在字符串末尾增加一个“0”。

4)“/”:将字符串最后一位删除。

(2)源程序。

#include <stdio.h>

char num[100000000]={0},op[6000000];

int main()

{

int n,m;

scanf("%d%d",&n,&m);

scanf("%s%s",num,op);

for (int k=0;k<m;k++)

{

int i;

switch(op[k])

{

case '+': for (i=n-1;num[i]!='0'; i--)

num[i]='0';

num[i]='1';

break;

case '-': for (i=n-1;num[i]!='1'; i--)

num[i]='1';

num[i]='0';

break;

case '*': num[n++]='0';  num[n]='\0';

break;

case '/': num[--n]='\0';

}

}

printf("%s\n",num);

return 0;

}

26-3  完全二叉搜索树

问题描述

二叉搜索树BST(Binary Search Tree)是这样一棵树,它或者是一棵空树,或者是一棵具有下列特性的非空二叉树:

(1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。

(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。

(3)它的左、右子树也分别为二叉搜索树。

若一棵二叉树既是一棵满二叉树,又是一棵二叉搜索数,则这棵树是一棵完全二叉搜索树。例如,图1给出的就是一棵由1~15共15个整数构成的完全二叉搜索树。

图1 一棵完全二叉搜索树

设有一棵由整数1~构成的完全二叉搜索树,编写一个程序,输入一个整数num(),输出在完全二叉搜索树中以该整数为根结点的子树的所有结点值中的最小值和最大值。例如,输入12,输出9和15;输入14,输出13和15;输入13,输出13和13。

输入格式

一个整数num。

输出格式

两个整数,分别表示以整数num为根结点的子树的所有结点值中的最小值和最大值。

输入样例

12

输出样例

9 15

(1)编程思路。

将个整数的完全二叉搜索树先构造出来,然后找到整数num所在的结点p,则以p结点为根的子树的中序遍历序列的第1个结点就是所求的最小值、中序遍历的最后一个结点就是所求得最大值。这样虽然能够解决问题,但显然不是一个好的办法。

将图1所示的完全二叉搜索树中整数全部写成二进制数,可以发现:

1)奇数全部在最底层。最底层数据的二进制数的最右边一定是1(即=1)。

2)倒数第2层为2的倍数,其二进制数据的最右边只有一个0,即=0、=1。

3)倒数第3层为4的倍数,其二进制数据的最右边有两个0,即=0、=0、=1。

4)倒数第4层为8的倍数,其二进制数据的最右边有三个0,即=0、=1。

将整数num(num=6、10、14、4、12、8)及所求的最小值和最大值列成如表1所示的表格。

表1 以num为根结点的BST的最小值和最大值(括号中为对应二进制数)

num

最小值

最大值

6  (0110)

5  (0101)

7  (0111)

10 (1010)

9  (1001)

11 (1011)

14 (1110)

13 (1111)

15 (1111)

4  (0100)

1  (0001)

7  (0111)

12 (1100)

9  (1001)

15 (1111)

8  (1000)

1  (0001)

15 (1111)

观察表1中的二进制数据,不难得出结论:

1)以二进制数 X 为根的子树的最小值是将 X 最右之 1 换成 0,再加 1 所得的数。

2)以二进制数 X为根的子树的最大值是将 X 最右之1 右边的 0 全换成 1 所得的数。

设二进制数X最右边有连续k个0,若连续k个1组成的二进制数为P,则按上面的结论:最小值为X-P,最大值为X+P。

(2)源程序。

#include <stdio.h>

int main()

{

int a,p;

scanf("%d",&a);

for(p=2;a%p==0;p*=2);

p=p/2-1;

printf("%d %d\n",a-p,a+p);

return 0;

}

C语言程序设计100例之(26):二进制数中1的个数的更多相关文章

  1. 黑马程序员——经典C语言程序设计100例

    1.数字排列 2.奖金分配问题 3.已知条件求解整数 4.输入日期判断第几天 5.输入整数进行排序 6.用*号显示字母C的图案 7.显示特殊图案 8.打印九九口诀 9.输出国际象棋棋盘 10.打印楼梯 ...

  2. C语言程序设计100例之(22):插入排序

    例22  插入排序 问题描述 排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素或记录的任意序列,重新排列成一个以关键字递增(或递减)排列的有序序列. 排序的方法有很多,简单插入排序就是一 ...

  3. C语言程序设计100例之(16):巧解算式

    例16  巧解算式 问题描述 在1.2.3.4.5.6.7.8.9.10个数中间加上加号或减号,使得到的表达式的值为自然数N,如果中间没有符号,则认为前后为一个数,如1 2 3认为是一百二十三(123 ...

  4. C语言程序设计100例之(12):Eratosthenes筛法求质数

    例12   Eratosthenes筛法求质数 问题描述 Eratosthenes筛法的基本思想是:把某范围内的自然数从小到大依次排列好.宣布1不是质数,把它去掉:然后从余下的数中取出最小的数,宣布它 ...

  5. C语言程序设计100例之(14):丑数

    例14   丑数 问题描述 丑数是其质因子只可能是2,3或5的数.前10个丑数分别为1, 2, 3, 4, 5, 6, 8, 9, 10, 12.输入一个正整数n,求第n个丑数. 输入格式 每行为一个 ...

  6. C语言程序设计100例之(23):数列求和

    例23  数列求和 问题描述 已知某数列前两项为2和3,其后继项根据前面最后两项的乘积,按下列规则生成: ① 若乘积为一位数,则该乘积即为数列的后继项: ② 若乘积为二位数,则该乘积的十位上的数字和个 ...

  7. C语言程序设计100例之(21):折半查找

    例21  折半查找 问题描述 顺序查找是一种最简单和最基本的检索方法.其基本思想是:从检索表的一端(如表中第一个记录或最后一个记录)开始,逐个进行记录的关键字和给定值的比较.若某个记录的关键字和给定值 ...

  8. C语言程序设计100例之(13):最大子段和

    例13        最大子段和 题目描述 给出一段序列,选出其中连续且非空的一段使得这段和最大.例如在序列2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,-1,2. 输入格式 第一 ...

  9. C语言程序设计100例之(9):生理周期

    例9    生理周期 问题描述 人生来就有三个生理周期,分别为体力.感情和智力周期,它们的周期长度为 23 天.28 天和33 天.每一个周期中有一天是高峰.在高峰这天,人会在相应的方面表现出色.例如 ...

随机推荐

  1. 回环屏障CyclicBarrier

    上一篇说的CountDownLatch是一个计数器,类似线程的join方法,但是有一个缺陷,就是当计数器的值到达0之后,再调用CountDownLatch的await和countDown方法就会立刻返 ...

  2. OSI协议

    物理层: 网线连接在客户端计算机上,其实是连接在了计算机的一个叫做网卡的设备上,网卡是专门负责与外界通信的.网线一般是双绞线或者光缆,也可以使用无线电波,中间经过交换机,路由器,防火墙等等一堆设备统称 ...

  3. dmesg用法

    百科概念:dmesg是一种程序,用于检测和控制内核环缓冲.程序用来帮助用户了解系统的启动信息. 解释:dmesg命令显示linux内核的环形缓冲区信息,我们可以从中获得诸如系统架构.cpu.挂载的硬件 ...

  4. Python中的模块简单认识

    将自己定义的方法,变量存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件称为模块. 细说的话,模块可以分为四个通用类别: 1 使用python编写的.py文件(自定义模块) 2 已被编译为共 ...

  5. 记一次和“N+1”的擦肩而过

    这周五,就是昨天,部门走了3个人.他们是"被离职"的,从被通知到走人,只过了一周左右.一开始经理和他们谈的时候,说有没有赔偿不能确定,然后到周四左右,上面的人可能是等急了,才说年终 ...

  6. 题解【洛谷P1807】最长路_NOI导刊2010提高(07)

    题面 题解 最长路模板. 只需要在最短路的模板上把符号改一下\(+\)初值赋为\(-1\)即可. 注意一定是单向边,不然出现了正环就没有最长路了,就好比出现了负环就没有最短路了. 只能用\(SPFA\ ...

  7. 牛客竞赛第二场D Kth Minimum Clique 贪心+bitmap

    Kth Minimum Clique 题意 给出n(n<100)个点的邻接表,和n个点的权值,求第k大的团(完全子图) 分析 n很小,并且好像没有什么算法和这个有关系,所以可以往暴力枚举的方向想 ...

  8. springboot中druid监控的配置(DruidConfiguration)

    当数据库连接池使用druid 时,我们进行一些简单的配置就能查看到sql监控,web监控,url监控等等. 以springboot为例,配置如下 import com.alibaba.druid.su ...

  9. ASP.NET Core Web API中实现全局异常捕获与处理

    处理全局异常 HANDLING ERRORS GLOBALLY 在上面的示例中,我们的 action 内部有一个 try-catch 代码块.这一点很重要,我们需要在我们的 action 方法体中处理 ...

  10. SCROLLINFO结构详解

    在刚开始使用SCROLLINFO结构时感觉很不顺手,尤其其中的成员fMask理解不太深刻,经过查询资料才理解一二. 在使用滚动条功能时,如果要设置它的范围和位置可以用以前的函数,例如:SetScrol ...