《算法竞赛入门经典(第2版)》第二章笔记、习题及思考题

《算法竞赛入门经典》习题源码 Github开源:https://github.com/RyanHe123/Classic-Introduction-to-Algorithmic-Competition
本文中的习题题解为本人完成,如果存在错误或可以改进的地方,欢迎在评论区提出,谢谢!

  • 疑问:P19

    #include<stdio.h>
    int main()
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            printf("%d\n",i);
        return 0;
    }
    

    上面的代码里有一个重要的细节,变量 i 定义在循环语句中,因此 i 在循环体中不可见,例如,在第8行,之前再插入一条printf("%d\n",i);会报错。

    笔者认为应该是i在循环体外不可见。

  • 建议尽量缩短变量的定义范围,例如在for循环的初始化部分定义循环变量

  • 三种取整方法:

    向上取整:ceil();

    向下取整:floor();

    四舍五入:round(); 或floor(n+0.5);

  • 浮点运算可能存在误差。在进行浮点数比较时,应考虑到浮点误差。

    判定平方数时:

    最好不要用if(sqrt(n)==floor(sqrt(n))),因为浮点数的运算有可能存在误差,例如假设在经过大量计算后,由于误差的影响,整数1变成了0.9999999999,floor的结果是0而不是1。所以为了减小误差的影响,一般改为四舍五入,但是小数部分为0.5的数也会受到浮点误差的影响,因此任何一道严密的算法竞赛题目中都需要想办法解决这个问题。

  • while和do-while的区别:

    1. while是先判断再循环 do-while是先循环再判断
    2. while可能未循环直接跳出,而do-while至少循环1次
  • 计时函数clock():

    可以使用time.h和clock()函数获得程序运行时间

    函数原型:

    clock_t clock(void) ;
    

    typedef long clock_t;

    也就是说clock_t是一个长整形,得到的时间除以常数CLOCKS_PER_SEC,得到的数以秒为单位

    该函数返回程序目前为止运行的时间

    为了避免输入数据的时间影响测试结果,使用一种称为“管道”的小技巧,在Windows命令行进入该项目的.exe的目录下,执行echo <输入数据>|<程序名称>。

  • 循环结构程序设计中最常见的两个问题:算术运算溢出程序效率低下

  • 在“多数据输入”的题目中,常犯的错误是一次计算后,未将数据重新初始化,所以要尽量缩短变量定义的范围

习题

习题 2-1水仙花数(daffodil)

输入100~999中的所有水仙花数。若3位数ABC满足ABC=A3+B3+C3,则称其为水仙花数。例如153=13+53+33,所以153是水仙花数。

#include<stdio.h>
int main()
{
	int n=100;
	int a,b,c;
	for(;n<1000;n++)
	{
		a=n/100;
		b=n%100/10;
		c=n%10;
		if(n==a*a*a+b*b*b+c*c*c)
		{
			printf("%d\n",n);
		}
		}	
 } 
习题2-2 韩信点兵(hanxin)

相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变化队形,而他每次只掠一眼队伍的排尾就知道总人数了。输入3个非负整数a,b,c,表示每种队形排位的人数(a<3,b<5,c<7),输出总人数的最小值(或报告无解)。已知总人数不小于10,不超过100。输入到文件结束为止。

样例输入:

2 1 6
2 1 3

样例输出:

Case 1: 41
Case 2: No answer
#include<stdio.h>
int main()
{
	int kase=1;
	int a,b,c;
	while(scanf("%d %d %d",&a,&b,&c)==3)
	{
		int state=0;
		int i;
		for(i=10;i<101;i++)
		{
			if(i%3==a&&i%5==b&&i%7==c)
			{
				state=1;
				printf("Case %d: %d\n",kase++,i);
				break;
			}
		}
		if(state==0)
		{
			printf("Case %d: No answer\n",kase++);
		}
	}
	return 0;
}
习题2-3 倒三角形(triangle)

输入正整数n<=20,输出一个n层的倒三角形。例如n=5时输出如下:

#########
  #######
​    #####
​      ###
​        #

#include<stdio.h>
int main()
{
	int n;
	scanf("%d",&n);
	int i=n;
	for(;i>0;i--)
	{
		int j;
		for(j=0;j<n-i;j++)
		{
			printf(" ");
		}
		for(j=0;j<2*i-1;j++)
		{
			printf("#");
		}
		printf("\n");
	}
	return 0;
}
习题2-4 子序列的和(subsequence)

输入两个正整数n<m<106,输出 1 n 2 + 1 ( n + 1 ) 2 + . . . + 1 m 2 \frac{1}{n^2}+\frac{1}{(n+1)^2}+...+\frac{1}{m^2} n21+(n+1)21+...+m21,保留5位小数。输入包含多组数据,结束标记为n=m=0。注意:本题有陷阱

样例输入:

2 4
65536 655360
0 0

样例输出:

Case 10.42361
Case 20.00001
//Version 1
#include<stdio.h>
int main()
{
	int n,m;
	int kase=0;
	while(scanf("%d %d",&n,&m)==2&&n&&m)
	{
		int i;
		double sum=0.0;
		for(i=n;i<m+1;i++)
		{
			sum+=(double)1/i/i;
			//此处如果使用(i*i),数据会溢出 
		}
		printf("Case %d: %.5lf\n",++kase,sum);
	}
}

笔者后来在print()函数详解中发现了这样的用法:

利用*来占位,然后在后面的参数列表中给出最小宽度,所以本程序可以进行简化:

//Version 2
#include<stdio.h>
int main()
{
	int a,b,c;
	int kase=0;
	while(scanf("%d %d %d",&a,&b,&c)==3&&a&&b&&c)
	{
		double res;
		res=(double)a/b;
		printf("Case %d:%.*f\n",++kase,c,res);
	}
	return 0;
 } 
习题2-5 分数化小数(decimal)

输入正整数a,b,c,输出a/b的小数形式,精确到小数点后c位。a,b$\leqKaTeX parse error: Double superscript at position 5: 10^6^̲,c\leq$100。输入包含多组数据,结束标记为a=b=c=0。

样例输入:

1 6 4
0 0 0

样例输出:

Case 1: 0.1667
#include<stdio.h>
int main()
{
	int a,b,c;
	int kase=0;
	while(scanf("%d %d %d",&a,&b,&c)==3&&a&&b&&c)
	{
		double res;
		res=(double)a/b;
		char s[15]="Case %d:%.cf\n";
		s[10]='0'+c;
		printf(s,++kase,res);
	}
	return 0;
 } 
习题2-6 排列(permutation)

用1,2,3,…,9组成3个三位数abc,def和ghi,每个数字恰好使用一次,要求abc:def:ghi=1:2:3。按照“abc def ghi”的格式输出所有解。提示:不必太动脑筋。

#include<stdio.h>
int main()
{
	int n1,n2,n3;
	int a,b,c,d,e,f,g,h,i;
	int count[10];
	n1=123;
	for(;n1<330;n1++)
	{
		n2=n1*2;
		n3=n1*3;
		int i;
		for(i=0;i<10;i++)
			count[i]=0;
		count[n1/100]++;
		count[n1%100/10]++;
		count[n1%10]++;
		count[n2/100]++;
		count[n2%100/10]++;
		count[n2%10]++;
		count[n3/100]++;
		count[n3%100/10]++;
		count[n3%10]++;
		int state=1;
		for(i=1;i<10;i++)
		{
			if(count[i]!=1)
			{
				state=0;
				break;
			}
		}
		if(state==1)
		{
			printf("%d %d %d\n",n1,n2,n3);
		}
	}
}

思考题:

题目1:

假设需要输出 2 , 4 , 6 , 8 , . . . , 2 n 2,4,6,8,...,2n 2,4,6,8...,2n,每个一行,能不能通过改动下文的程序实现?

#include<stdio.h>
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
 		printf("%d\n",i);
    return 0;
}

任务1: 修改第7行,不修改第6行。

printf("%d\n",2*i);

任务2: 修改第6行,不修改第7行。

for(int i=2;i<=2*n;i+=2)

题目2:

下面程序的运行结果是什么?

#include<stdio.h>
int main()
{
    double i;
    for(i=0;i!=10;i+=0.1)
        printf("%.1f\n",i);
    return 0;
}

运行后发现循环并不能停止,笔者搜索后发现是浮点数陷阱

参考链接:http://blog.sina.com.cn/s/blog_6da76f9b0100yr8d.html

浮点数陷阱:

    之所以无限循环,那么就可以推断是for循环的条件始终成立,即 i 始终不等于10. 但是 i 是从0开始的,每次都自加0.1.那么应该是100次后就停止.为什么i始终不等于10呢? 是因为浮点数的原因. 我们把10改成10.0,结果仍然是无限循环. 我们把 i += 0.1 改成 i++,发现执行10次后正常停止.我们可以初步推断,是浮点数的加法运算引起的.

    接下来调用gdb输出中间结果来观察,发现 i 自加0.1后,并不是我们预想的等于0.1而是等于 0.10000000000000001.往下执行几次,i 的值分别是0.20000000000000001 0.30000000000000004 0.40000000000000002.

    这样,我们就理解了为什么i 始终不等于10. 因为浮点数在进行小数运算的时候由于精度问题,会有很小的误差,然而用 = 或者 != 这样的运算符来比较,是会检测出这种误差的.所以导致结果的不正确.

    我们还可以多测试一下,将循环条件改为 i != 0.1 或者 i != 0.2时,程序能够正常运行,得到正常结果.但是当i != 0.3时,就是无限循环.显然,在我们的程序中,这种不确定的错误是不应该存在的.

    因此,在定义循环变量时,尽量采用int型及整数的加减.因为循环的本质意义就是通过各种条件来控制语句重复运行次数.而这个次数本身就是整数.要实现小数的功能尽量通过循环中的语句来实现。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值