《算法竞赛入门经典》读书笔记 第二章 循环结构程序设计

来源:互联网 发布:网易评论知乎 编辑:程序博客网 时间:2024/04/29 09:28

《算法竞赛入门经典》读书笔记

第二章 循环结构程序设计

2.1 For循环

知识点1:For循环语法结构

 for(int i = 1; i <= n;i++)

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

(1)For循环语法  for ( 初始化; 条件; 调整) 循环体;

建议利用IDE的“单步调试”功能了解循环实现细节;

调试步骤:设置断点à调试à 添加查看变量à单步(下一步)调试

(2)结束循环的条件是“条件”不再满足;

(3)循环变量的声明位置

      定义在循环内,外部不可访问!

      for(int i = 1; i <= n; i++)

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

      printf("%d\n",i);   //编译出错,不能访问i

 

      定义在循环外,外部可继续访问,但注意“副效应”!

      int i;

      for(i = 1; i <= n;i++)

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

      printf("%d\n",i);  //这里希望i的为n,可结果却为n + 1

 

课堂练习:

(1)打印一行n个星号

     输入样例:5

     输出样例:*****

(2)打印m行n列的星型矩阵

     输入样例:3 5

     输出样例:*****

               *****

               *****

 

 

知识点2:多重For循环语法结构

for  ( 初始化; 条件; 调整 )             //外循环

    for ( 初始化; 条件; 调整 )           //内循环

          循环体;

建议通过“单步调试”观察外循环与内循环变量的变化规律

 

知识点3:浮点数误差

(1)通常,在程序设计中涉及浮点数相关运算时,应考虑“浮点数误差”,譬如不能直接写:

double i;

if (i == 0){  }  //判断i是否为原点

而应写成:

if ( i<= 1e-6) {   }

(2)floor()  表示下取整函数,floor(x+0.5)表示对x进行四舍五入处理;

 

知识点4:continue与break

continue表示“直接进行下一次循环”;

break 表示“直接跳出当前(层)循环”

 

知识点5: for ( 初始化; 条件; 调整) 循环体; 

(1)括号中的任何一个部分都可以省略

(2) “条件”不写或干脆写为for(;;)的形式,如果不在循环体中作任何退出控制(如break),它就是死循环;

(3)不要在循环体中修改循环变量的值,以免产生隐患,例如:

for ( int i = 1; i<=n;  i++)

{

     i++;

}

 

知识点6:“穷举法”及其优化(aabb问题)

(1)      穷举法的算法思想即将问题的所有可能解一一列举,然后逐一验证是否满足条件。

(2) 穷举法是万能的,但没有优化的穷举是万万不能的。优化之一即是降低循环变量范围。

#include<stdio.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;

}

 

 

#include<stdio.h>

int main()

{

    for(int x=32;x<=99 ; x++)

    {

      int n = x * x;

    

      int hi = n / 100;

      int lo = n % 100;

      if(hi/10 == hi%10 && lo/10 == lo%10) printf("%d\n",n);

   }

    return 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


2.2 While循环和do-while循环

知识点1:三种循环的适用场合

(1)当循环次数已知时,通常使用for循环实现;当循环次数未知时,则通常使用While循环、do-while循环;

(2)任何一种for循环都可转换为while循环

譬如:

for  ( 初始化; 条件; 调整 )  循环体;  

等价于

初始化;

While(条件)

{

              循环体;

调整;

}

(3)while与do-while的本质区别

    while(先判断)                  do

    {                              {

           后执行                        先执行;

}                             }while(再判断);

类比“感冒了”的问题解决策略:

    while(还继续感冒发热)         do

    {                             {

          吃点药                       吃点药;

}                              }while(还继续感冒发热);                                 

知识点2:计数器

当需要统计某种事物的个数时,可以用一个变量充当计数器;推而广之,如果需要统计一批事物各自的个数时,则可用一批变量(譬如:数组)作为计数器。

 

知识点3:程序运行结果出错的处理思路

(1)   静态查错;

(2)   输出中间结果

(3)   IDE或gdb调试

 

知识点4:数据溢出的处理思路

(1)最终结果不溢出,但中间计算结果可能会溢出;

(2)如果(int)溢出,则可换用存储更大数据范围的数据类型(long long),或者利用同余定理:要计算只包含加法、减法和乘法的整数表达式除以正整数n的余数,可以在每步计算之后对n取余,结果不变。

 

2.3 循环的代价

知识点1:循环体内的初始化

for(int i = 1;i <= n; i++)

   {

      int factorial = 1;    //这句话发挥什么作用?

      for(int j = 1; j <= i;j++)

        factorial *= j;

      S += factorial;

}

 

intfactorial = 1;    //能写在这里吗?

for(int i = 1;i <= n; i++)

   {

       for(intj = 1; j <= i;j++)

           factorial*= j;

        S += factorial;

}

 

(1)在循环体开始处定义的变量,每次执行循环体时会重新声明并初始化;

(2)上述阶乘之和暴露两个问题:效率低下、数据溢出。

 

知识点2:计时函数

 printf("Time used =%.2lf\n", (double)clock()/CLOCKS_PER_SEC);

注:(1)需事先引入头文件 #include<time.h>

(2)通过“打点计时”可大致估测数据规模与时间之前的关系。

(3)为了避免输入数据的时间影响测试结果,可以采用“管道”技术。在命令行下执行echo20 | abc,操作系统会自动把20输入,其中abc是程序名。

 

知识点3:利用“递推”缓解“效率低下”

n! = (n-1)! * n,说明后一个数的阶乘计算完全可以利用前一个数的阶乘结果完成,大大减少不必要的计算。

 

 int factorial = 1;  

 for(inti = 1;i <= n; i++)

 {

    factorial *= i;

    S += factorial;

 }

注:这里有着“记忆化搜索”的味道!

 

知识点4:利用“同余定理”缓解“数据溢出”

利用同余定理:要计算只包含加法、减法和乘法的整数表达式除以正整数n的余数,可以在每步计算之后对n取余,结果不变。

余数定理:

a:两数的和除以m的余数等于这两个数分别除以m的余数和。

实例:7÷3=…1,5÷3=…2,这样(7+5)÷3的余数就等于1+2=3,所以余0。

b: 两数的差除以m的余数等于这两个数分别除以m的余数差。

实例:8÷3=…2,4÷3=…1,这样(8-4)÷3的余数就等于2-1=1,所以余1。

如果是(7-5)÷3呢?    会出什么问题?

c: 两数的积除以m的余数等于这两个数分别除以m的余数积。

实例:7÷3=…1,5÷3=…2,这样(7×5)÷3的余数就等于1×2=2,所以余2。

   性质:

同余定义:

若两个整数a,b被自然数m除有相同的余数,那么称a,b对于模m同余,用式子表示为

     a≡b(mod m)    (*)   同余式(*)意味着(我们假设a≥b)a-b=mk,k是整数,即m|(a-b)

若两个数a,b除以同一个数c得到的余数相同,则a,b的差一定能被c整除。这条性质非常有用,一定要熟练掌握。

 

2.4 算法竞赛中的输入、输出框架

例题:a+b问题,这是最简单的了,请看题目

题目描述  Calculate a+b.

输入      Two integer a,b (0<=a,b<=10).

输出      Output a+b.

示例输入

1 2

示例输出

3

知识点1:ACM输入输出(见附录)

知识点2:NOIP文件输入输出

#include < stdio.h >

int main()

{

int a,b;

scanf("%d %d",&a, &b);

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

return 0;

}

 

假设 输入文件名:data.in

输出文件名:data.out

#include<stdio.h>

int main()

{

    freopen("data.in","r",stdin);

    freopen("data.out","w",stdout);

   

scanf(“%d%d”,&a,&b);

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

   

    return 0;  

}

 

知识点2:格式化输入的返回值

例如 scanf(“%d”,&a) == 1 表示成功输入的参数个数为1

 scanf(“%d%d”,&a,&b) == 2 表示成功输入的参数个数为2

 

特别的

while(scanf("%d %d",&a, &b) != EOF)

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

表示一直输入到文件结束

 

知识点3:“打擂台”解决最值问题

(1) 变量在未赋值前的值是不确定的,特别地,它不一定等于0.

(2) 最大值、最大值的初始化

const int INF = 1000000000;

int max = -INF, min = INF;   //人为假想一个最大(小)值

 

或者 scanf(“%d”,x);   //读入第一个数,并假设其为最大(小)值

     max = x;

     min = x;

 

 

附:

ACM中的输入输出框架

在开始做ACM时,会面临一个输入输出数据的问题,ACM里的输入输出数据和平时的写程序不大一样。为什么会不一样呢,这就牵涉到评测系统怎么判断你提交的程序是正确的。实际上评测系统是把程序的标准输入输出数据都是放在文本文件里,你提交的程序会先经过编译,然后运行,从输入文件中读取数据,然后把结果输出到一个文本文件中,评测系统再把标准的输出文件和你提交的程序运行的结果的输出文件进行对比,从而判断你提交的程序的正确与否。既然是这样,要判断提交的程序的正确性就依赖于系统的测试数据,这时就不可能只是用一组测试数据来判断程序的正确性,需要有很多组测试数据,而国际ACM比赛标准的评测系统是PC2,它只支持一个题目一个输入数据文件、一个输出数据文件。所以这时候就得把多组测试数据放在一个文件里,提交的程序必须把这个文件里的多组输入数据都得出结果。

一、输入:

1、只有一组测试数据,这时候是最简单的了,请看题目

题目描述  Calculate a+b.

输入      Two integer a,b (0<=a,b<=10).

输出      Output a+b.

示例输入

1 2

示例输出

3

 

实现代码:

C

C++

#include < stdio.h >

int main()

{

     int a,b;

     scanf("%d %d",&a, &b);

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

     return 0;

}

 

#include < iostream >  

using namespace std;

int main()

{

     int a,b;

     cin >> a >> b;

     cout << a+b << endl;

     return 0;

}

 2、有多组测试数据,直到读至输入文件结尾为止,这时需要用到while(scanf("%d",&n)!=EOF)或while (cin>>n),请看题目:

示例输入

1 5
10 20

示例输出

6
30

 

 

C

C++

#include < stdio.h >

int main()  

{

      int a,b;

      while(scanf("%d %d",&a, &b) != EOF) 

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

      return 0;

}

#include < iostream >   

using namespace std;

int main()

{

       int a,b;

       while(cin >> a >> b)

            cout << a+b << endl;

       return 0;

}

说明:scanf函数返回值就是读出的变量个数,如:scanf( “%d %d”, &a, &b );如果只有一个整数输入,返回值是1,如果有两个整数输入,返回值是2,如果一个都没有,则返回值是-1。EOF是一个预定义的常量,等于-1。

 

3、在开始的时候输入一个N,接下来是N组数据,请看题目:

示例输入

2
1 5
10 20

示例输出

6
30

 

C

C++

#include<stdio.h>

int main()

{

int n,i;

int a,b;

scanf("%d",&n);

for(i=0;i<n;i++)

{

scanf("%d%d",&a,&b);

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

}

return 0;

}

#include <iostream>

using namespace std;

int main() {

    int a, b, n;

    cin >> n;

    while (n--) {

        cout << a + b << endl;

    }

    return 0;

}

#include<stdio.h>

int main() {
    int n, i;
    int a, b;
    scanf("%d", &n);
    while (n--) {
        scanf("%d%d", &a, &b);
        printf("%d\n", a + b);
    }
    return 0;
}

 

 

4、       输入不说明有多少组数据,但以某个特殊输入为结束标志。请看题目:

示例输入

1 5
10 20
0 0

示例输出

6
30

 

这个题目是以0 0 代表输入结束。

C

C++

#include <stdio.h>
int main()
{
    int a,b;
    while(scanf("%d %d",&a, &b) &&(a||b))
         printf("%d\n",a+b);
}

 

#include<iostream>
using namespace std;
int main()
{
    int a ,b;
    while(cin>>a>>b&&(a||b))
    {cout<<a+b<<endl;}
    return 0;
}

 

5、       还有一种是前几种的组合,请看题目:

示例输入

4 1 2 3 4

5 1 2 3 4 5

0

示例输出

10
15

 

C

C++

#include<stdio.h>
int main()
{
    int n,sum,a;
    while(scanf("%d",&n) && n)
    {
         sum=0;
         while(n--)
         {
             scanf("%d",&a);
             sum+=a;
         }
         printf("%d\n",sum);
    }
    return 0;
}

#include<iostream>
using namespace std;
int main()
{
    int n,sum,a;
    while(cin>>n&&n)
    {
         sum=0;
         while(n--)
         {
             cin>>a;
             sum+=a;
         }
         cout<<sum<<endl;
    }
    return 0;
}  

 

 

  6、输入字符串。  

    输入是一整行的字符串的,C语法:
    char buf[20];
    gets(buf);
    C++语法:
    如果用string buf;来保存:
           getline( cin , buf );
    如果用char buf[ 255 ]; 来保存:
          cin.getline( buf, 255 );
       scanf(“ %s%s”,str1,str2),在多个字符串之间用一个或多个空格分隔;
    若使用gets函数,应为gets(str1); gets(str2); 字符串之间用回车符作分隔。
    通常情况下,接受短字符用scanf函数,接受长字符用gets函数。
     而getchar函数每次只接受一个字符,经常c=getchar()这样来使用。
    getline 是一个函数,它可以接受用户的输入的字符,直到已达指定个数,或者用户输入了特定的字符。它的函数声明形式(函数原型)如下:
          istream& getline(char line[], int size, char endchar = '\n');
    不用管它的返回类型,来关心它的三个参数:
    char line[]: 就是一个字符数组,用户输入的内容将存入在该数组内。
    int size : 最多接受几个字符?用户超过size的输入都将不被接受。
    char endchar :当用户输入endchar指定的字符时,自动结束。默认是回车符。
    结合后两个参数,getline可以方便地实现: 用户最多输入指定个数的字符,如果超过,则仅指定个数的前面字符有效,如果没有超过,则用户可以通过回车来结束输入。
    char name[4];cin.getline(name,4,'\n');
    由于 endchar 默认已经是 '\n',所以后面那行也可以写成:
    cin.getline(name,4);
    最后需要说明的是,C++的输入输出流用起来比较方便,但速度比C要慢得多。在输入输出量巨大时,用C++很可能超时,应采用C的输入输出。

 

二、输出:

  输出有不同的格式要求,不注意的话经常会出现“PresentationError”,而且PC2很多时候还判断不出来输出格式错误,就简单的判为"WrongAnswer",所以输出格式一定要注意。

1、一组输出接着一组输出,中间没有空行,这也是最简单的,请看题目

示例输入

1 5

10 20

示例输出

6
30

 

 

#include < stdio.h >
int main()   //main函数定义成int类型
{
      int a,b;
      while(scanf("%d %d",&a, &b) !=EOF)
            printf("%d\n",a+b);
      return 0;

每输出一组结果换行就可以了。

 

2、一组接着一组,每一组后面有一空行。请看

示例输入

1 5

10 20

示例输出

6
 
30

 

#include<stdio.h>

int main() {
    int a, b;
    while (scanf("%d%d", &a, &b) !=EOF)
        printf("%d\n\n", a + b);
    return 0;
}

每输出一组结果后输出两个换行就可以了。

 

3、一组接着一组,每两组之间有一个空行,注意与前一种区分开,请看题目

示例输入

3
4 1 2 3 4
5 1 2 3 4 5
3 1 2 3

示例输出

10
 
15
 
6

#include<stdio.h>

int main() {
    int n,m,sum,a;
    int i;
    scanf("%d",&n);
    for (i = 0; i < n;i++) {
        scanf("%d",&m);
        sum = 0;
        while (m--){
            scanf("%d",&a);
            sum +=a;
        }
        printf("%d\n",sum);
        if (i != n-1)
            printf("\n");
    }
    return 0;
}
 

判断是否到达最后一组测试数据了,如果不是最后一组测试数据就多输出一个换行

 

 

0 0
原创粉丝点击