一.概述
第二章主要是介绍了循环结构在程序中的使用,同时重点介绍了文件操作(重点是通过文件对数据的输入输出).参见这里.
由于在算法比赛中对数据的输入输出有着严格的规定,因此如何正确有效地使用这些方法是我们需要注意和多加练习的地方.
二.例题分析
例题2-1涉及到了如何判断整数的问题,可以参见这里.
/*例题2-3*/
思路: 仍然是循环的使用,只不过循环语句不是简单语句,而是计算阶层,是另外
一个循环,因此用二重循环就能解决了.
#include<stdio.h> int main() { int s = 0,i,j,m,n; scanf("%d",&n); for(i = 1; i <= n; i++) { m = 1; for(j = 1; j <= i; j++) { m *= j; } s += m; } printf("%d/n",s % 1000000); return 0; }
#include<stdio.h> #include<time.h> int main() { const int MOD = 1000000; int i,j,n,m,s=0; scanf("%d", &n); for(i = 1; i <= n; i++) { m = 1; for(j = 1; j <= i ; j++) m = (m * j % MOD); s = (s + m) % MOD; } printf("%d/n",s); printf("Time used = %.2lf/n", (double)clock() / CLOCKS_PER_SEC); return 0; }
由于上面的程序在n比较大的时候会产生溢出,得不出正确结果.所以改用另种方法.
这里需要一点数学知识:要计算只包含加法,减法和乘法的整数表达式除以正整数n
的余数,可以在每步计算之后对n取余,结果不变.
注意到这个程序用到了头文件time.h中的clock()函数.该函数返回程序目前为止运行
的时间,这个时间除以常数CLOCKS_PRE_SEC_之后得到的值以"秒"为单位.
还需注意一点:常数CLOCKS_PER_SECC和操作系统相关,不要直接使用clock()的返回值,
而应总是除以CLOCKS_PER_SEC.
并且clock() / CLOCKS_PER_SEC返回的是从程序开始到结束的时间,也就是说输入数据
的时间也被计算在内,显然,这是我们不希望的.这里可以用命令echo x | test echo
运用了"管道"的知识, 而x是输入的数据,test是程序生成的可执行文件名.
这样就相当于系统自动地输入数据,将手动输入数据的时间造成的误差排除了.
/*例题2-4*/
#include<stdio.h> #define INF 1000000 int main() { int x,n,sum,min,max; min = INF; max = -INF; sum = 0; n = 0; while(scanf("%d",&x) == 1) { if(x >= max) max = x; if(x <= min) min = x; sum += x; n ++; } printf("%d %d %.3lf/n",min,max,(double)sum / n); return 0; }
分析:
1. 首先遇到的问题是:如何在没给出输入确定个数的变量的输入问题.即不知道变量的个数,
就无法定义变量的确定名称.
其实,就算知道了变量的个数,但是这个数量是很大的话,利用scanf来控制输入也是不方
便的(当然,可以采取数组的形式).因此必须采用一种更加方便的方法.
就是 while(scanf("%d",&x)) 这种语句.由于scanf返回值是int型,只要有输入返回就是
成功的变量个数(不等于0).就不断循环.当然,如果输入完毕,可以人为结束:在windows下
先按enter确定输入完毕,再按组合键Ctrl + Z,再按enter键确认.在linux下,按组合键
Ctrl + D;
2. 接下来的问题就是max和min的初值问题:一开始要设置max与min为什么值才能与待输入的
数据正确地比较呢?
一种比较直观的方法是把max设为一个很小的值,把min设为一个很大的值.但是,这样做有
一定的风险,因为你不能确定接下来输入的数据到底有多大,或者有多小(本题中做了约束
是1000).比如你设min初值是1000.但是接下来输入的数据都是比1000大的数据,那么最小
值结果就是错误的.
另一种比较稳妥的方法是将max与min都赋予第一个输入的数据的值,这样,无论如何都不会
出现上面描述的那种情况了.但是程序要稍长一点.
/*练习2.4.2*/
#include<stdio.h>
int main()
{
double i;
for(i = 0; i != 0.2; i = i + 0.1)
printf("%.1lf/n", i);
return 0;
}
浮点数陷阱:
该程序的运行结果是: 无限循环.
之所以无限循环,那么就可以推断是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型及整数的加减.因为循环的本质意义就是通过
各种条件来控制语句重复运行次数.而这个次数本身就是整数.要实现小数的功能尽量
通过循环中的语句来实现.
/*练习2.4.3*/
/*重定向 + 计时 */
#define LOCAL #include<stdio.h> #include<math.h> #include<time.h> int main() { #ifdef LOCAL freopen("c://data.in","r",stdin); freopen("c://data.out","w",stdout); #endif int i,m,n,x = 0; scanf("%d",&n); m = floor(sqrt(n)); for(i = 1; i <= m; i++) { if(n % i == 0) x++; } printf("%d/n",x * 2); printf("Time used = %.2lf/n",(double)clock() / CLOCKS_PER_SEC); return 0; }
/* int 型数据版本 */
#include<stdio.h> #include<math.h> int main() { int n,i,m,x = 0; scanf("%d",&n); m = floor(sqrt(n)); for(i = 1; i <= m; i++) { if(n % i == 0) x++; } printf("%d/n",x * 2); return 0; }
/* long long数据类型版本 */
#include<stdio.h> #include<math.h> int main() { long long n,i,m,x = 0; scanf("%I64d",&n); m = floor(sqrt(n)); for(i = 1; i <= m; i++) { if(n % i == 0) x++; } printf("%I64d/n",x * 2); return 0; }
分析:
第一个 重定向 + 计时版本,把前面的重定向文件操作和计时函数联系起来
第二个是 int型数据版本, 但是不能满足n <= 10^12这个条件.可以通过上机实验,
发现当输入数据是10^9时,输出是100,而增加到10^10时,输出是44.明显是数据溢出
造成程序结果不正确
第三个是 long long型数据版本,类型的容量可以达到10^19(略窄).就能满足题目的
n <= 10^12 的条件了.注意此时的输入输出格式: %I64d. 但是在linux下的gcc和
vs2008都是使用%lld.
/*练习2.4.4*/
/*cstdio版本*/ /* #include<cstdio> using namespace std; int main() { int a,b; while(scanf("%d%d",&a,&b) == 2) printf("%d /n",a+b); return 0; } */ /*iostream版本*/ /* #include<iostream> using namespace std; int main() { int a,b; while(cin >> a >> b) cout << a+b << endl; return 0; } */ /*cin的输入,fout的输出*/ #include<fstream> #include<iostream> using namespace std; //ifstream fin ("aplusb.in"); ofstream fout("aplusb.out"); int main() { int a,b; while(cin >> a >> b) fout << a + b << endl; return 0; }
分析:
上面的cstdio与原来的stdio.h版本的c程序无非就是前面加了c后面去掉了.h,并且多
了一条语句using namespace std;其他的差不多.
而后面的iostream版本输入输出明显和原来的不一样,改成了cin和cout.少去了格式控
制符的介入.
而cin和fout的混合使用也体现了c++能同时使用文件流和标准输入输出流操作的方便,
显然这样比freopen的优势就在于在程序中能同时使用标准输入输出了.
另:我在C-Free下编译时,出现了错误,仔细寻找了下,发现是文件名是.c为后缀的c文件
编译时就出错,改成了.cpp为后缀后,错误就不存在了.不过我估计在其他编译器中
不会出现这样的情况.个例而已.
三.习题参考答案
/*习题2-1*/ /*重定向版 #include<stdio.h> #define LOCAL int main() { #ifdef LOCAL freopen("2-1.in","r",stdin); freopen("2-1.out","w",stdout); #endif int i,j = 0,n = 1; scanf("%d",&i); while(i / n) { n *= 10; j ++; } printf("%d/n",j); return 0; } */ /*fopen版 c版本 #include<stdio.h> int main() { FILE *fin, *fout; fin = fopen("2-1.in","r"); fout = fopen("2-1.out","w"); int i,j = 0,n = 1; fscanf(fin,"%d",&i); while(i / n) { n *= 10; j ++; } fprintf(fout,"%d/n",j); fclose(fin); fclose(fout); return 0; } */ /*fopen版本 c++版本*/ #include<fstream> using namespace std; ifstream fin("2-1.in"); ofstream fout("2-1.out"); int main() { int i,j = 0,n = 1; cin >> i; while(i / n) { n *= 10; j ++; } cout << j << endl; return 0; }
/*习题2-2*/ /*注: 题目描述有点问题,应该是ABC = A^3 + B^3 +C^3 而不是 ABC = A^2 + B^2 + C^2*/ /*重定向版 #include<stdio.h> #define LOCAL int main() { #ifdef LOCAL freopen("2-2.out","w",stdout); #endif int i,j,k,n,m; for(i = 1; i <= 9; i++) for(j = 0; j <= 9; j++) for(k = 0; k <= 9; k++) { n = i*100 + j*10 + k; m = i*i*i + j*j*j + k*k*k; if(m == n) printf("%d/n",n); } return 0; } */ #include<stdio.h> #define LOCAL int main() { #ifdef LOCAL freopen("2-2.out","w",stdout); #endif int i,j,k,n,m; for(i = 1; i <= 9; i++) for(j = 0; j <= 9; j++) for(k = 0; k <= 9; k++) { n = i*100 + j*10 + k; m = i*i*i + j*j*j + k*k*k; if(m == n) printf("%d/n",n); } return 0; }
/*习题2-3*/ /*重定向版本 #include<stdio.h> #define LOCAL int main() { #ifdef LOCAL freopen("2-3.in","r",stdin); freopen("2-3.out","w",stdout); #endif int a,b,c,i; scanf("%d%d%d",&a,&b,&c); for(i = 10; i <= 100; i++) { if(i%3 == a && i%5 == b && i%7 == c) { printf("%d/n",i); break; } } if(i == 101) printf("No answer/n"); return 0; } */ /*fopen版本 #include<stdio.h> int main() { FILE *fin,*fout; fin = fopen("2-3.in","r"); fout = fopen("2-3.out","w"); int a,b,c,i; fscanf(fin,"%d%d%d",&a,&b,&c); for(i = 10; i <= 100; i++) { if(i%3 == a && i%5 == b && i%7 == c) { fprintf(fout,"%d/n",i); break; } } if(i == 101) fprintf(fout,"No answer/n"); fclose(fin); fclose(fout); return 0; } */ /*c++文件流版本*/ #include<fstream> using namespace std; ifstream fin("2-3.in"); ofstream fout("2-3.out"); int main() { int a,b,c,i; fin >> a >> b >> c; for(i = 10; i <= 100; i++) { if(i%3 == a && i%5 == b && i%7 == c) { fout<< i << endl; break; } } if(i == 101) fout << "No answer" << endl; return 0; }
/*习题2-4*/ /*重定向*/ #include<stdio.h> #define LOCAL int main() { #ifdef LOCAL freopen("2-4.in","r",stdin); freopen("2-4.out","w",stdout); #endif int n,i,j,k = 0; scanf("%d",&n); for(i = 0; i <= n; i++) { while(k < i) { printf(" "); k++; } for(j = 1; j <= 2*(n-i)-1; j++) { printf("*"); } printf("/n"); k = 0; } return 0; }
#include<fstream> #include<iostream> using namespace std; ifstream fin("2-6.in"); ofstream fout("2-6.out"); int main() { int n,i; double h = 0; fin >> n; for(i = 1; i <= n; i++) { h += (double)1/i ; } fout.precision(4); fout << h; return 0; }
分析:
这里使用的c++的文件流进行文件读写,将fout定义为ofstream流.但是这里
有一个精度控制问题.
使用fout.precision()是能够控制精度,但是控制的是整个数的有效位数.当整数
位是1位时,小数位是3位.但是当整数位是2位时,小数位就是2位了.
在网上查的资料都是使用cout怎么样控制的文章.setf(fixed)这样是固定点方式
显示,这种形式下控制的精度precision就是控制小数点后的精度了.但是在文件流
中似乎不能这样用.
/*习题2-7*/ #include<stdio.h> #include<math.h> #define LOCAL int main() { #ifdef LOCAL freopen("2-7.out","w",stdout); #endif double i = 1,x = 1,pi = 1; int flag = -1; while(fabs(x) >= pow(10,-6)) { i += 2; x = (double)1/(i); pi += flag*x; flag *= -1; } printf("%.8lf/n",pi); printf("%.9lf/n",x); return 0; }
#include<stdio.h> int main() { FILE *fin,*fout; fin = fopen("2-9.in","r"); fout = fopen("2-9.out","w"); int a,b,c; fscanf(fin,"%d%d%d",&a,&b,&c); fprintf(fout,"%.*lf",c,(double)a/b); return 0; }
分析: 该题本身没有难度,但是间接地给我们提供了printf的一种很强大的使用方法,
即通过变量来控制精度.我们一般在使用printf进行格式控制的时候,都是按照
给定的精度进行控制,比如精确到小数点后三位.而使用变量来控制精度呢?
在printf中可以使用类似于通配符的*来实现.
符号*是用后面参数表给出的变量进行替代.因此,%.*lf可通过与*对应的变量
来控制小数点后的位数.
剩下几题没有完美的解决方案,过段时间贴上来 :D