zoukankan      html  css  js  c++  java
  • 算法编程常见错误

    算法考试,一共七道题,只做了两道,在第三题就卡住了,完全可以说是非常糟糕的成绩.直接原因在于没有将数据的类型由int转换到long long,其实当时不是没有考虑过这个问题,但是最终还是因为计算出了偏差而被自己否决了,这里做一个记录提醒一下自己.同时希望可以借此机会梳理一下c++(c)算法编程中的常见错误即对应的解决策略

    一、问题回顾

    Description

    学校有好几个食堂,每当中午下课吃饭时,人会很多,菜品也有很多,而且每个同学的口味也可能不同,同一寝室或同一班级的同学相互之间关系也不错,所以每个同学买的菜不一定仅仅给自己吃,有的还需要帮其他同学打包。现在知道菜品的种数以及每种菜品打菜所需时间,如果还告诉你每个同学打菜的菜品,共有n同学排队要买自己喜欢吃的饭菜(包括帮其他同学打包的菜),请你求出一种排队次序,使所有同学买好饭菜的时间总和为最小(单位为分钟,以下同,需要指出的是除了第一个学生不用等待,直接买菜外,后面的同学所需买好菜的时间包括等待和打菜的时间,一个同学打完菜后后面的同学才能打菜)。
    

    Input

    输入有多组测试数据,对于每组数据,输入有三部分,第一部分只有一行是用空格隔开的两个正整数m和n,其中m(1<=m<=10000)表示学生的总人数,n(1<=n<=26)表示菜的品种数;第二部分也只有一行是n个用空格隔开的正整数,分别表示n种菜品各自需要的打菜时间(1<=打菜时间<=100),第三部分有m行,每行是一个由大写字母组成的字符串(大写字母可以相同或重复出现,但字符串长度不超过100,即打菜的菜品可以重复,但总份数不超过100),每个大写字母表示学生需要打的菜品,A表示第一种菜品,B表示第二种菜品,以此类推,一个同学不同的菜品打菜的先后顺序不影响后面同学的等待时间。
    

    Output

    对于每组输入,输出只有一行,表示m个同学买好饭菜所需时间的总和的最小值。
    

    Sample Input

    3 5
    3 1 4 2 5
    ACE
    BBA
    ABDB
    

    Sample Output

    41
    

    分析问题

    1. 题目其实很简单:

      • 先将每个字符串序列按权相加,如:A,B,C,D,E分别对应3,1,4,2,5,可得个字符串的总权值依次为:12,5,7
      • 根据题意,我们应该排序是最小权重的作业优先完成:5,7,12
      • 计算等待时间和买菜权值的总和:(5)+(5+7)+(5+7+12)=41
    2. 数据范围估计(思考极限数据范围):

      • 每道菜的权值在1~26之间,一个人最多能买100道菜:26*100=2600
      • 最多有10000个人,但是由于等待时间可以叠加,所以最大的数据范围是:(2600*10000^2>2.7e9)
      • 以上可以看出是爆了int的,对应的处理时将最终的数据结果存储给为long long即可
      #include <iostream>
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <algorithm>
      
      using namespace std;
      
      const int N=10000;
      int arr[110],res[N+10];
      char str[110];
      
      int main()
      {
          int n,m;
          while(~scanf("%d %d",&m,&n)){
              memset(res,0,sizeof res);
              memset(arr,0,sizeof arr);
              for(int i=1;i<=n;i++){
                  scanf("%d",&arr[i]);
              }
              for(int i=1;i<=m;i++){
                  scanf("%s",str+1);
                  int len=strlen(str+1);
                  for(int j=1;j<=len;j++){
                      res[i]+=arr[str[j]-'A'+1];
                  }
              }
              sort(res+1,res+m+1);
              long long ans=0;
              res[0]=0;
              for(int i=1;i<=m;i++){
                  //cout << res[i] << endl;
                  res[i]=res[i]+res[i-1];
                  ans+=res[i];
              }
              printf("%d
      ",ans);
          }
          return 0;
      }
      

    二、常见错误分析

    Compilation Error

    1. 介绍:编译错误

    2. 常见的错误类型及解决方法:

      • 程序错误

        简介:即程序本身的一些问题

        解决:建议现在本地编译器调试运行好之后,个人认为编译器如CodeBlocks自带的调试功能是非常重要的,学会使用这个东西可以很好地帮助你debug,这往往也是初学者容易忽视的

      • 编译器类型选择错误

        介绍:有时我们可以在编译中看到多个类似选项,比如有多个c++的版本

        解决:

        • 相信大家也有所了解,诸如C++现在常用的万能头#include <bits/stdc++.h>并不是所有的c++版本都支持的,所以这个时候一般选择最新版提交即可.
        • 由于c++基本是对c兼容的,还有非常实用的stl库,所以一般建议直接使用c++的编译选项

    Memory Limit Exceed

    1. 介绍:内存超限.事实上这个错误并不常碰到,因为现在内存的容量发展得太快了,现今的许多算法主要是对时间有比较高的要求,许多算法降低时间的代价也是基于提高内存的开销——即空间换时间,显然这大抵也是值得的.如果说需要非要和内存有紧密的联系,那么通常是为了考察动态数组,链表等知识点.此外,需要了解的是实际可以开的内存大小:这里以64MB的内存,4个字节的int型数组为例,(64/4=16),(16 imes1024 imes1024approx1.7e7),考虑到程序的运行还需要还需要分配内存给栈结构等,所以实际可以开的内存大小约为(1.6e7)
    2. 解决:使用C++中的vector或链表等动态数组结构

    Presentation Error

    1. 介绍:通常是输出格式的错误

      解决:

      • 采用复制粘贴的形式保证输出的字符正确性(千万别太相信自己!)
      • 对于1 2 3 4这样以空格分隔的输出
        • 通常对1进行单独输出,然后循环输出空格i即可,
        • 另一个不错的输出格式:printf("%d%c",i,i==len?' ':' ');

    Run TIme Error

    1. Run TIme Error

    2. 介绍:运行时错误

    3. 常见的错误类型及解决方法:

      • 除数为0

        简介:在程序运行过程中出现了除数为0的操作,这个时候需要仔细审视一下所给数据的边界值是否有准确处理到

        解决:特判处理

      • 待补充…

    Segmentation Fault

    1. 介绍:段错误,该类型的错误通常与数组的使用有关

    2. 常见错误类型及解决方法:

      • 数组下标访问越界

        简介:通常是你访问的数组下标超过了你定义的长度.但是以int arr[100]为例,如果你尝试用arr[-1]这样显然无效的地址进行访问时是不会报错的,可以正常访问输出;但如果用arr[10000],那么就会报Segmentation Faultde的错误了.其原因在于arr[]这样的封装其实是一个“语法糖”.arr本质上是一个const的指针,指向了一个固定的位置,arr[-1]=arr+(-1)*4,4是int的字节数.搞清楚这一点再结合操作系统的知识,大致能够想到Segmentation Fault的错误原因其实是越过了操作系统给定的程序界地址!

        解决:修正数组下标索引的范围

      • 栈溢出

        简介:通常我们在使用dfs进行搜索时需要用到堆栈结构.每一次向下递归,都需要将函数内部的变量保存在栈中;每一次向上回溯,就会将栈中对应的数据取出.显然,栈大小不是无限的,当递归的层数太多时就会造成栈溢出的问题

        解决:通常这个时候就要考虑算法的可行性,优化算法或者是排查递归出口的有效性

      • 指针的无效访问

        简介:虽然我们很少在写算法的时候使用指针,但是在一些大作业中经常会遇到的错误便是空指针的异常调用.如p是一个结构体类型的指针,data是该结构的元素,那么我们在进行p->data形式的访问时需要保证p指针不为NULL

        解决:包括先判断p指针是否为空等多种形式

    Time Limit Exceed

    1. 介绍:常见的错误之一,原因可以有两个方面,一是算法的复杂度超过了题目的限制,这个时候需要思考的如何优化算法(在写算法之前应该简单计算一下时间复杂度,这也是常规操作了),可以尝试在原代码的基础上,但大部分时候恐怕是你的使用的算法没有达到出题人的期望;又或者数据中有出现死循环.
    2. 解决:
      • 如果是因为算法的问题,想就是了
      • 如果是因为死循环的问题,需要好好再检查一下代码,如果不是ACM的罚时机制,你甚至可以尝试将代码以二分的形式注释然后提交运行,如果在某一情况下报错变成了"WA",那么恭喜你——你可能找到错误的原因了.

    Wrong Answer

    1. 介绍:最常见的错误,也就是我们俗成的"wa了",通常情况下是程序自身逻辑的错误,但也有一些错误是由于不符合程序规范而导致的

    2. 常见错误类型及解决方法:

      • 数据类型错误

        简介:通常是数据精度的问题,常见的错误就是"爆int",因为这种数据范围的错误不会使程序中断并抛出异常,所以我们在做题目之前需要适当地进行一下估算(特别是对于一些极限的边界)

        解决:修改数据类型,如:long long

      • 没有实现多组输入

        简介:在最初写程序时,我们往往是输入一组的内容就可以结束程序了,如:pta中许多题目就是如此.但是,显然这和我们在工程中遇到的实际问题是不相符的.如:计算a+b时,我们更希望可以持续地输入输出.

        解决:面对这一问题,我们往往只要使用一个统一的格式模板即可

        while(~scanf("%d %d",&a,&b)){
        	//...
        }
        

        或者

        while(scanf("%d %d",&a,&b)!=EOF){
        	//...
        }
        

        解释(这里查了一些零散的资料进行整合):

        • scanf的返回值:

          (1).正数:输入值正确接收的个数(这与scanf中的格式和输入的形式有关)

          (2).0:输入的格式完全错误

          (3).-1:读取到了文件的结束标记

        • ~:按位取反

          -1的补码:

          • 先求原码(绝对值的二进制形式):1000 0000 … 0001(共32位)
          • 求反码(负数的反码为符号位不变,其它各位取反):1111 1111 … 1110
          • 求补码(负数的补码+):1111 1111 … 1110
          • 使用~取反:0000 0000 … 0000(循环退出)
        • EOF(End Of File)

          查看头文件可知

          #ifndef EOF
          # define EOF (-1)
          
      • 数据初始化

        简介:数据初始化问题是采用多组输入导致的并发问题,因为单组输入可以认为是程序的初始化,但在多组输入的情况下需要我们主动的对常量,数组进行初始化

        解决:

        • 对于普通的变量,可以通过将变量定义在while(~scanf())循环的内部

        • 对于数组:我们通常使用memset(arr,0,sizeof arr),即将数组arr的sizeof arr(sizeof 是一个运算符,在c++中可以不需要加()直接调用,返回的是数组arr所占的字节数)大小的字节单位用0进行填充,该操作通常用于对数组的全0初始化

          需要注意的是:memset是按字节进行赋值的,如arr是一个int型的数组,32位,共4个字节,倘若用1填充,那么数组的每个元素的值将会是0000 0001 0000 0001 0000 0001 0000 0001

        补充:局部变量需要手动初始化,否则变量的值为"随机数";全局变量不需要手动初始化,其初值为0(无论是数值类型的数组还是字符类型的数组,初值均为0——自己理解一下).

    最后,祝大家都能AC!

    码字不易,欢迎点赞支持!

  • 相关阅读:
    【2020-01-28】陪伴即陪伴,擦汗即擦汗
    【2020-01-27】曼巴走了,但他还在
    【2020-01-26】今年,远亲不如近邻了
    【2020-01-25】新的一年,新的传统
    【2020-01-24】上天为这小女孩开了一扇小小窗
    【2020-01-23】故作假装的毛病
    day 31 html(二) 和css入门
    前端 day 30 html 基础一
    day 17python 面对对象之继承
    多并发编程基础 之协成
  • 原文地址:https://www.cnblogs.com/Arno-vc/p/14340295.html
Copyright © 2011-2022 走看看