zoukankan      html  css  js  c++  java
  • 读入优化

    「笔记」读入优化

    终极模板:

     1 #include<cstdio>
     2 class Read
     3 {
     4 private:
     5     inline char getc()
     6     {
     7         static char buf[1<<18], *fs, *fe;
     8         return (fs == fe && (fe = (fs = buf)+fread(buf, 1, 1<<18, stdin)), fs == fe)? EOF: *fs++;
     9     }
    10 public:
    11     inline int operator()()
    12     {
    13         int x = 0, flag = 1;
    14         char c;
    15         c = getc();
    16         while(c<'0' || c>'9')
    17         {
    18             if(c == '-')
    19                 flag = -1;
    20             c = getc();
    21         }
    22         while(c>='0' && c<='9')
    23         {
    24             x = x*10+c-'0';
    25             c = getc();
    26         }
    27         return x*flag;
    28     }
    29 }readin;
    View Code

      用法类似  x = readin()

    /**************************************************************************************************************************************************************************************/

    「笔记」读入优化

    更新摘要

    本文于2018/10/28更新一次,内容如下:

    • 将模板中定义的read更改为readin,因为在某些网站评测时(如COGS)使用read会与系统关键词重名,保险起见进行更正。
    • Top 1与Top 2的一句话对比。

    • 以及,经过少量的实验WA,发现如果输入要转化为长整型时,使用快读可能反而拖慢速度,但这一情况仍未经系统验证。

    以下是正文

    背景提要

    本以为这辈子也不会用快读的……结果被某YYS原创题的LDY毒瘤数据给坑了,所以就稍微研究了一下OI中对大数据读入的优化。真香

    以下各方法大致按速度由慢到快排序,其中前半部分半代码临文手打,不保证能跑起来

    常规操作

    常规操作是最常用的方法。虽然速度略慢,但胜在泛用性广。

    Top 4. cin

    如题,即C++中iostream的cin读入,最方便,但速度也最慢,对数据量大的题目大概率T。

    1
    2
    3
    4
    5
    6
    7
    #include <iostream>
    int main()
    {
    int x;
    std::cin >>x;
    return 0;
    }

    Top 3. scanf || 关闭同步的cin

    一般情况下,cin无法处理的情况可以直接用scanf,速度快上不少,而且格式化读入或许会有些意想不到的好处。

    1
    2
    3
    4
    5
    6
    7
    #include <cstdio>
    int main()
    {
    int x;
    scanf("%d", &x);
    return 0;
    }

    当然,如果你还是喜欢cin的话,这里有一个STL的黑魔法来给cin提速。事实上,iostream本身的速度是可以与stdio匹敌的,然而为了兼容性,默认会开启iostream与stdio的同步,这大大减慢了iostream的速度。故,我们可以手动关闭同步,从而为iostream提速。提速后的iostream与stdio速度差不多,但注意不要与stdio混用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <iostream>
    int main()
    {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    int x;
    cin >>x;
    return 0;
    }

    黑魔法

    虽然Top 3能满足多数需求,但我们时常会遇到题目输入大量整数的情况,数据规模动不动就几百几千万。面对这样的大数据,常规的方法就难免超时了,这时,我们就需要对输入进行优化。

    注意,以下方法仅针对只有整数读入,而浮点数的读入原理相似,如有需要,自行百度。

    Top 2. 普通快读

    由于scanf在读入时对数据类型进行了很多无用的判断,在输入只有整数时,我们可以手动用getchar函数读入字符,然后将字符处理成整数。实现过程大致就是依次读入字符,若为数字,就将其添加在整数末尾,并用flag变量来判断正负。这就是一般快读的思路,其思路和代码都比较简单,而且比scanf快几倍,可以当作模板背下来。

    但是,经过少量的实验重复提交,发现如果输入要转化为长整型时,使用快读可能反而拖慢速度,但这一情况仍未经系统验证,不保证正确性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include <cstdio>
    inline int read()
    {
    int x = 0, flag = 1;
    char c = getchar();
    while(c < '0' || c > '9')
    {
    if(c == '-')
    flag = -1;
    c = getchar();
    }
    while(c >= '0' && c <= '9')
    {
    x = x*10+c-'0';
    c = getchar();
    }
    return flag*x;
    }
    int main()
    {
    int x;
    x = read();
    return 0;
    }

    拒绝压行,如有压行需要自行解决

    Top 1. 急速劲读

    代码

    虽然Top 2已经很快了,但是江湖中还流传着一种比之更快的快读。

    这种快读的实现不是很容易想到,我们先看代码,再逐步解释:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #include <cstdio>
    inline char getc()
    {
    static char buf[1<<18], *fs, *fe;
    return (fs == fe && (fe = (fs = buf)+fread(buf, 1, 1<<18, stdin)), fs == fe)? EOF: *fs++;
    }
    int read()
    {
    int x = 0, flag = 1;
    char ch;
    ch = getc();
    while(ch<'0' || ch>'9')
    {
    if(ch == '-')
    flag = -1;
    ch = getc();
    }
    while(ch>='0' && ch<='9')
    {
    x = x*10+ch-'0';
    ch = getc();
    }
    return x*flag;
    }
    int main()
    {
    int x;
    x = read();
    return 0;
    }

    不难发现,read函数和Top 2的读入函数几乎相同。而关键就在于多出的getc函数。这两大行乍一看很难懂,我们一步步解释。

    前置知识

    对于一般快读来说,我们用getchar代替scanf进行读入,从而加速。然而,多次打开关闭输入流也是要耗时的,于是我们可以更进一步,减少从输入流读入的次数,从而让快读更加的暴力劲爽。可以借助缓冲区的概念,即先将数据读入一个“缓冲区”中,然后再对“缓冲区”进行操作。

    所以我们要用stdio的这个函数:

    1
    size_t fread(void *buf, size_t size, size_t nmemb, FILE *stream)

    其四个参数分别为:读入的数组、读入的每个元素的大小、元素的个数、读入的文件流。返回值为成功读取的元素的总数。

    所以我们可以直接用fread(buf, 1, 1<<18, stdin)从标准输入中读入大量的数据,存入一个数组。这样我们后面就可以直接从数组中“读入”,而不是再次从输入流中读入。

    详解

    getc函数压行严重,我们先将其展开:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    inline char getc()
    {
    static char buf[1>>18], *fs, *fe;
    if (fs == fe)
    {
    fs = buf;
    fe = fs + fread(buf, 1, 1>>18, stdin);
    if (fs == fe)
    return EOF;
    }
    return *fs++;
    }

    先看第一行:static char buf[100000], *fs = buf, *fe = buf;

    static声明了静态变量,静态变量的生命周期直至程序结束,且初始化只有第一次有效,可当作全局变量。其中,buf是缓冲数组,fs、fe分别代表起始指针和末尾指针。

    在第一次调用函数时,fs和fe均指向buf开始位置,于是进入if语句块中。接着对fs进行更新,接着执行fe = fs + fread(buf, 1, 1>>18, stdin);,即从标准输入中读入大量数据存入buf。由于fread返回的是读入元素的总数,故最终fe就指向了buf中最后一个元素的位置。

    此时,经过一次读入,若仍有fs == fe的话,则说明没有读入任何数据,即输入已经读入完毕,故直接返回EOF

    最后,return *fs++;,这句先返回fs指向的元素,然后将fs递增,实现“读入一个字符”的效果。在之后的调用中,fs不断递增,直至fe位置。此时再调用函数时,便会进入if语句块,再进行一次读入。

    综上,getc函数代替了常规快读中的getchar,从而使速度又得到了一个提升。

    接下来,让我们对这个函数压压行,使之缩成开始时那鬼畜一行。

    变量的声明直接跳过,看下面部分。

    先看这两行:

    1
    2
    fs = buf;
    fe = fs + fread(buf, 1, 1>>18, stdin);

    我们知道,赋值表达式的值就是等号右边的值。所以我们可以将这两行合并:

    1
    fe = (fs = buf)+fread(buf, 1, 1<<18, stdin)

    于是就剩下了两个if。其中,外层的if我们可以用? :代替,而内层的if,由于需要先重新算fs和fe,所以我们将外层判断和上面这行放在一起。具体而言,我们可以用&&将判断和赋值放在一起,从而利用&&短路的特性实现先判断,若判断成功再赋值。

    1
    fs == fe && (fe = (fs = buf)+fread(buf, 1, 1<<18, stdin))

    然后要加上内层的if,我们直接用逗号运算符把两块连接在一起:

    1
    (fs == fe && (fe = (fs = buf)+fread(buf, 1, 1<<18, stdin)), fs == fe)

    最后加上? :

    1
    return (fs == fe && (fe = (fs = buf)+fread(buf, 1, 1<<18, stdin)), fs == fe)? EOF: *fs++;

    至此,这种方法讲解完毕。但最后要注意的是,使用这种方法的话,就不能再用任何stdio或iostream的读入了,所以在使用前一定要好好权衡一下。这也是该方法的局限所在,相对而言,Top2虽然较慢,但泛用性更强,也算是各分千秋了。

    Top 0. 光速超读

    是的……还有比急速劲读更快的方法,当然实际上就是急速劲读的改进,做法不难想到:直接将所有数据一次性读入,然后一次性处理,这里不再阐述,如果对速度有更高的要求的话,可以参考这篇文章

    最终模板

    最终的模板,我选择了Top1,将其整合在一个类中,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include<cstdio>
    class Read
    {
    private:
    inline char getc()
    {
    static char buf[1<<18], *fs, *fe;
    return (fs == fe && (fe = (fs = buf)+fread(buf, 1, 1<<18, stdin)), fs == fe)? EOF: *fs++;
    }
    public:
    inline int operator()()
    {
    int x = 0, flag = 1;
    char c;
    c = getc();
    while(c<'0' || c>'9')
    {
    if(c == '-')
    flag = -1;
    c = getc();
    }
    while(c>='0' && c<='9')
    {
    x = x*10+c-'0';
    c = getc();
    }
    return x*flag;
    }
    }readin;

    用法类似x = readin()

  • 相关阅读:
    第19章 辅助器方法
    第18 章 视图
    第16章 过滤器
    第15章 控制器和动作
    第14章 高级路由特性
    第13章 URL 路由
    第12章 MVC项目综述
    第8章 SportsStorePeta 导航
    第9章 SportsStorePeta 完成购物车
    第10章 SportsStorePeta 管理
  • 原文地址:https://www.cnblogs.com/liuyongliu/p/10320615.html
Copyright © 2011-2022 走看看