2.1for循环
例题2-1 aabb
输出所有形如aabb的4位完全平方数(即前两位数字相等,后两位数字也相等)。
方法一:
#include<stdio.h>
#include<math.h>
int main()
{
for(int a=1;a<=9;a++)
for(int b=0;b<=9;b++)
{
int n=a*1100+b*11;
int m=floor(sqrt(n)+0.5); //向下取整 floor函数。为了减小误差的影响,一般改为四舍五入。
if(m*m==n) printf("%d\n",n);
}
return 0;
}
方法二:
#include<stdio.h>
#include<math.h>
int main()
{
for(int x=1;;x++)
{
int n=x*x;
if(n<1000) continue;
if(n>9999) break;
int hi=n/100;
int lo=n%100;
if(hi/10==hi%10&&lo/10==lo%10) printf("%d\n",n);
}
return 0;
}
输出结果:
7744
floor函数
floor(x),有时候也写做Floor(x),其功能是“向下取整”,或者说“向下舍入”,即取不大于x的最大整数(与“四舍五入”不同,下取整是直接取按照数轴上最接近要求的值左边的值,也就是不大于要求的值的最大的那个)。
continue 和 brak
continue是指跳回for循环的开始,执行调整语句并判断循环条件(即“直接执行下一次循环”)
break是指直接跳出循环(即“退出循环”)
2.2 while循环和do-while循环
int32位
在当前流行的竞赛平台中,int都是32位整数,范围是-2^31-2^31-1。
例题2-2 3n+1问题
猜想:对于任意大于1的自然数n,若n为奇数,则将n变为3n+1,否则变为n的一半。 经过若干次这样的变换,一定会使n变为1。例如,3→10→5→16→8→4→2→1。
输入n,输出变换的次数。n≤10^9
#include<stdio.h>
int main()
{
int n2,count=0;
scanf("%d",&n2);
long long n=n2;
while(n>1)
{
if(n%2==1) n=3*n+1;
else n/=2;
count++;
}
printf("%d\n",count);
return 0;
}
不要忘记测试
一个看上去正确的程序可能隐含错误。注意乘法溢出问题。
long long
范围是 -2^63~2^63-1。
1e-6
10^-6次方。
2.3 循环的代价
#include<stdio.h>
#include<time.h>
int main()
{
const int MOD =1000000;//使用代号而不是常数,改善了程序的可读性,也方便修改
int n,S=0;
scanf("%d",&n);
if(n>25) n=25; //通过找规律,发现了从40开始,答案不变,效率和溢出将不存在问题。
for(int i=1;i<=n;i++)
{
int factorial=1;
for(int j=1;j<=i;j++)
factorial=factorial*j%MOD;
S=(S+factorial)%MOD ;
}
printf("%d\n",S);
printf("Time used = %.2f\n", (double)clock()/CLOCKS_PER_SEC); //该函数返回程序目前为止运行的时间,这个时间除以常数 CLOCKS_PER_SEC之后得到的值以“秒”为单位。
return 0;
}
2.4算法竞赛中的输入输出框架
例题2-5 数据统计I
输入一些整数,求出它们的最小值、最大值和平均值(保留3位小数)。输入保证这些 数都是不超过1000的整数。
样例输入:
2 8 3 5 1 7 3 6
样例输出:
1 8 4.37
//数据统计(重定向版)
#define LOACL
#include<stdio.h>
#define INF 1000000000
int main()
{
#ifdef LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
int x,n=0,min=INF,max=-INF,s=0;
while(scanf("%d",&x)==1)
{
s+=x;
if(x<min) min=x;
if(x>max) max=x;
//printf("x=%d ,min = %d, max = %d\n",x,min,max);
n++;
}
printf("%d %d %.3f\n",min,max,(double)s/n);
return 0;
}
//数据统计(fopen版)
#include<stdio.h>
#define INF 1000000000
int main()
{
FILE *fin,*fout;
fin=fopen("data.in","rb");
fout=fopen("data.out","wb");
int x,n=0,min=INF,max=-INF,s=0;
while(fscanf(stdin,"%d",&x)==1)
{
s+=x;
if(x<min) min=x;
if(x>max) max=x;
n++;
}
fprintf(stdout,"%d %d %.3f\n",min,max,(double)s/n);
fclose(fin);
fclose(fout);
return 0;
}
比赛前应了解和遵守
请在比赛之前了解
文件读写的相关规定:是标准输入输出(也称标准I/O, 即直接读键盘、写屏幕),还是文件输入输出?如果是文件输入输出,是否禁止用重定向方式访问文件?
在算法竞赛中,选手应严格遵守比赛的文件名规定,包括程序文件名和输入输出文件名。不要弄错大小写,不要拼错文件名,不要使用绝对路径或相对路径。
在算法竞赛中,有经验的选手往往会使用条件编译指令并且将重要的测试语句注释掉而非删除。
在算法竞赛中,如果不允许使用重定向方式读写数据,应使用fopen和 fscanf/fprintf进行输入输出。
重定向和fopen
重定向和fopen两种方法各有优劣。重定向的方法写起来简单、自然,但是不能同时读写文件和标准输入输出;fopen的写法稍显繁琐,但是灵活性比较大(例如,可以反复打开 并读写文件)。顺便说一句,如果想把fopen版的程序改成读写标准输入输出,只需赋值“fin =stdin;fout=stdout;”即可,不要调用fopen和fclose。
结束输入
在Windows下,输入完毕后先按Enter键,再按Ctrl+Z键,最后再按Enter 键,即可结束输入。在Linux下,输入完毕后按Ctrl+D键即可结束输入。
例题2-6 数据统计II
输入一些整数,求出它们的最小值、最大值和平均值(保留3位小数)。输入保证这些 数都是不超过1000的整数。
输入包含多组数据,每组数据第一行是整数个数n,第二行是n个整数。n=0为输入结束 标记,程序应当忽略这组数据。相邻两组数据之间应输出一个空行。
样例输入:
8
2 8 3 5 1 7 3 6
4
-4 6 10 0
0
样例输出:
Case 1: 1 8 4.375
Case 2: -4 10 3.000
//数据统计(有bug)
#include<stdio.h>
#define INF 1000000000
int main()
{
int x,n=0,min=INF,max=-INF,s=0,kase=0; //需要定义为kase,如果为case会出错的。
while(scanf("%d",&n)==1&&n) //为了鲁棒性
{
int s=0;
for(int i=0;i<n;i++)
{
scanf("%d",&x);
s+=x;
if(x<min) min=x;
if(x>max) max=x;
}
if(kase) printf("\n");
printf("Case %d: %d %d %.3f\n",++kase,min,max,(double)s/n);
}
return 0;
}
注:bug在于min和max没有重置,仍然是上个数据结束后的值。
外层嵌套的s并未使用。
输出结果:
8
2 8 3 5 1 7 3 6
Case 1: 1 8 4.375
4
-4 6 10 0
Case 2: -4 10 3.000
0
鲁棒性
鲁棒是Robust的音译,也就是健壮和强壮的意思。它是在异常和危险情况下系统生存的关键。比如说,计算机软件在输入错误、磁盘故障、网络过载或有意攻击情况下,能否不死机、不崩溃,就是该软件的鲁棒性。所谓“鲁棒性”,是指控制系统在一定(结构,大小)的参数摄动下,维持其它某些性能的特性。
多数据题目应注意
在计算完一组数据后某些变量没 有重置,影响到下组数据的求解。
当嵌套的两个代码块中有同名变量时,内层的变量会屏蔽外层变量,有时会引起十分隐蔽的错误。
//数据统计(修改后)
#include<stdio.h>
#define INF 1000000000
int main()
{
int x,n=0,kase=0;//需要定义为kase,如果为case会出错的。
while(scanf("%d",&n)==1&&n)//为了鲁棒性
{
int s=0,min=INF,max=-INF;
for(int i=0;i<n;i++)
{
scanf("%d",&x);
s+=x;
if(x<min) min=x;
if(x>max) max=x;
}
if(kase) printf("\n");
printf("Case %d: %d %d %.3f\n",++kase,min,max,(double)s/n);
}
return 0;
}
2.5注释与习题
习题2-1 水仙花数(daffodil)
输出100~999中的所有水仙花数。若3位数ABC满足ABC=A^3+B^3+C^3,则称其为水仙花数。例如153=1^3+5^3+3^3,所以153是水仙花数。
#include<stdio.h>
int main()
{
for(int i=152;i<=999;i++)
{
int a=i/100;
int b=i/10%10;
int c=i%10;
if(i==a*a*a+b*b*b+c*c*c) printf("%d\n",i);
}
return 0;
}
输出结果:
153
370
371
407
习题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 a,b,c,kase=0;
while(scanf("%d",&a)&&scanf("%d",&b)&&scanf("%d",&c)==1) //此处需要注意
{
int i;
for(i=10;i<=100;i++)
{
if(i%3==a&&i%5==b&&i%7==c)
{
printf("Case %d: %d\n",++kase,i);
break;
}
}
if(i>100)
printf("No.answer\n");
}
return 0;
}
习题2-3 倒三角形(triangle)
输入正整数n≤20,输出一个n层的倒三角形。例如,n=5时输出如下:
#include<stdio.h>
int main()
{
int n;
scanf("%d",&n);
if (n < 1 || n > 20)
{
printf("Out of range[1, 20]\n");
return -1;
}
for(int line=0;line<n;line++)
{
for(int space=0;space<line;space++)
printf(" ");
for(int hash=0;hash <2*(n-line)-1;hash++)
{
printf("#");
}
for(int space=0;space<line;space++)
printf(" ");
printf("\n");
}
return 0;
}
习题2-4 子序列的和(subsequence)
输入两个正整数n<m<10^6,输出 ,保留5位小数。输入包含多组数据, 结束标记为n=m=0。提示:本题有陷阱。
样例输入:
2 4
65536 655360
0 0
样例输出:
Case 1: 0.42361
Case 2: 0.00001
#include<stdio.h>
int main()
{
int n,m,kase=0;
while(scanf("%d",&n)&&scanf("%d",&m)&&(m&&n))
{
if(n < m && n < 1e6)
{
double sum=0.0;
for(int i=n;i<=m ;i++)
{
sum= sum + 1.0/i/i; //连除而不是 /(i*i) ,陷阱就是在n特别大时如果直接n*n就会溢出,所以只能连除两次
}
printf("Case %d: %.5f\n",++kase,sum);
}
else
printf("input error");
}
return 0;
}
习题2-5 分数化小数(decimal)
输入正整数a,b,c,输出a/b的小数形式,精确到小数点后c位。a,b≤10^6,c≤100。输入包含多组数据,结束标记为a=b=c=0。
样例输入:
1 6 4
0 0 0
样例输出:
Case 1: 0.1667
#include<stdio.h>
#include<math.h>
int main()
{
int a,b,c,kase=0;
while(scanf("%d",&a)&&scanf("%d",&b)&&scanf("%d",&c)&&(a&&b&&c))
{
if(a<=1e6&&b<=1e6&&c<=100)
{
int x = floor((double)a / b);
int s;
printf("Case %d: %d.", ++kase,x);//输出整数部分
for (int i = 1; i < c; i++)
{
a = a * 10;
s = floor((double)a/b);
printf("%d", s%10);
a = a%b;
}
a = a * 10;
s = floor((double)a / b + 0.5); //最后一位进行四舍五入
printf("%d", s);
printf("\n");
}
else
printf("input error");
}
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>
#include <string.h>
int main(int argc, char *argv[])
{
int abc, def, ghi;
int a[10];
memset(a, 0, sizeof(a));
for(abc = 123; abc <= 329; abc++) //最大的ghi是987,987/3=329
{
int count = 0;
def = 2*abc;
ghi = 3*abc;
a[abc/100] = a[abc/10%10] = a[abc%10] = 1; //将所有可能出现的数字作为一个一维数组的下标,最后判断之和是否为9,如果小于9,必有重合,反之每个数字只有一个
a[def/100] = a[def/10%10] = a[def%10] = 1;
a[ghi/100] = a[ghi/10%10] = a[ghi%10] = 1;
int i;
for( i = 1; i <= 9; i++)
count += a[i];
if(count == 9) printf("%d %d %d\n", abc, def, ghi);
memset(a, 0, sizeof(a));
}
return 0;
}
memset(a,’0’,sizeof(a));
函数原型如下:
void *memset(void *s, int ch, size_t n);
函数解释:将s中前n个字节 (typedef unsigned int size_t)用 ch 替换并返回 s 。
这条语句是把a中所有字节换做字符“0”,常用来对指针或字符串的初始化。
需要添加头文件#include <string.h>
。