奇技淫巧:NOIP的读入优化

Posted 全宇宙唯一指定最菜的菜鸡的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了奇技淫巧:NOIP的读入优化相关的知识,希望对你有一定的参考价值。

最近看到洛谷上面有一个读入优化的代码:

inline char get_char(){//劲者快读
    static char buf[1000001],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline short read(){
    short num=0;
    char c;
    while(isspace(c=get_char()));
    while(num=num*10+c-48,isdigit(c=get_char()));
    return num;
}

说实话第一个函数get_char的第二行,这么长一六三目运算符真心看不懂

(下面的read函数里面那个isspace()和isdigit()就是判断这个字符是不是空格,是不是数字,是的就返回true,不是返回false。你看多没用的函数= =)

然后我就把代码百度了一下,发现还真有类似的东西:

inline char NC(void)
{
  static char buf[100000], *p1 = buf, *p2 = buf;
  if (p1 == p2) {
    p2 = (p1 = buf) + fread(buf, 1, 100000, stdin);
    if (p1 == p2) return EOF;
  }
  return *p1++;
}

于是研究了一会,发现这是一个极其神奇的读入优化

一般来说我们读入都用的scanf和cin,实在必要的时候可以用getchar读入优化。

然后众所周知,cin比scanf慢,getchar最快。

但是到底差距有多大很多人都不知道。

于是上个星期我做了一个贼有意思的测试,把scanf、cin、getchar(分为宏定义函数和内联函数两个)分别读入1000000个数,然后输出运行时间。

getchar的两个函数贴在这里:

#define gi(a) do { \\
  register char ch; \\
  while((ch = getchar()) > \'9\' || ch < \'0\'); \\
  for(a = ch-\'0\'; (ch = getchar()) >= \'0\' && ch <= \'9\'; a = a*10+ch-\'0\'); \\
  }while(0)
inline void gi2(int &a) {
    register char ch; 
    while((ch = getchar()) > \'9\' ||ch < \'0\'); 
    for(a = ch-\'0\'; (ch = getchar()) >= \'0\' && ch <= \'9\'; a = a*10+ch-\'0\'; 
}
//(仅限正整数)

发现scanf大概比cin快2倍,getchar比cin快5倍。

其中宏定义的getchar稍稍比内联的getchar快那么一点点点点。

然后就是今天我看到的玄学优化了。

https://www.byvoid.com/zhs/blog/fast-readfile

这里有所有读入的速度评估(看来我还不是第一个干这种贼有意思的事情的wwww)

经过实测,这种优化比scanf快了10倍!

这个读入用的是fread,我百度了一下,这是一种直接把文件所有字符全部读入的一个函数。

函数原型是这样的:

size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;

可以看到有四个参数,分别是读入的数组,读入每个数据项的字节数,读入的个数,以及读入的文件stream。

返回的是读到的数据项的个数。

此外还有个static关键字,就把它看成是函数里面的全局变量就行了,也就是说再用一次这个函数,里面的值不会变的。

此外static只有在第一次定义的时候才能赋值,之后调用的时候赋值是无效的,也就是说之后的调用函数这个赋值那一行是没用的。

这里还有个点,看这一行:

p2 = (p1 = buf) + fread(buf, 1, 100000, stdin);

这里的=号的返回值就是赋值号右边的那个值,也就是buf的值。

那么就等于写成这样:

p1 = buf; 
p2 = buf+ fread(buf, 1, 100000, stdin);

在我们一开始调用函数的时候,p1 p2都被赋值为buf的数组开始的位置,之后进入if语句,p2变成数据最后的位置(开始的位置+数据的长度 = 最后的位置)。

然后随着每次返回,p1都会往前面移动一个,直到p1也遍历到了最后的位置,p1 == p2

这时候再次进入if语句,然后同样p2变成数据最后的位置,因为p1这时也是数据最后的位置了,所以p1照样==p2,于是数据遍历结束了,返回文件结束符EOF.

就这样,完美模拟一次文件读写~

然后把if语句简化就变成了那个很长一六的三目运算符:

inline char get_char(){//劲者快读
    static char buf[1000001],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}

可以把这个模板背一下,然后就直接套自己的getchar读入优化模板了。

当然也可以把这里面的static索性全部拿到外面去,变成全局变量,然后做一个宏定义:

char buf[1000001],*p1=buf,*p2=buf;
#define get_char() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)

虽说这样肯定比内联函数快,但是因为宏定义毕竟是机械式替换的……所以不保证不出玄学bug,慎用。(不过一般来说用我自己写的模板不会有问题)

(其实我不会把只有一行语句的宏用do while(0)封装起来,所以只能打个括号完事)

这个优化虽然快是快,但是因为奇技淫巧……不保证不出bug,所以还是谨慎使用。

就当做普及一个玄学优化吧。

这里有一个大神写的整合版,大致原理是一样的,不过代码更简单,而且读入比我的又要快一倍(用的指针+一次性读入所有的整数),代码如下

const int MAXS = 60*1024*1024;
const int MAXN = 10000000;
char buf[MAXS];
int numbers[MAXN];

void analyse(char *buf,int len = MAXS)
{
    int i;
    numbers[i=0]=0;
    for (char *p=buf;*p && p-buf<len;p++)
        if (*p == \' \')
            numbers[++i]=0;
        else
            numbers[i] = numbers[i] * 10 + *p - \'0\';
}
void fread_analyse()
{
    freopen("data.txt","rb",stdin);
    int len = fread(buf,1,MAXS,stdin);
    buf[len] = \'\\0\';
    analyse(buf,len);
}

最后贴上我把这个玄学优化写进我的玄学getchar里面弄出的超级巨型玄学贼有意思奇技淫巧读入优化

char buf[1000001],*p1=buf,*p2=buf;
#define get_char() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
#define gi(a) do { \\
  register char ch; \\
  while((ch = get_char()) > \'9\' || ch < \'0\'); \\
  for(a = ch-\'0\'; (ch = get_char()) >= \'0\' && ch <= \'9\'; a = a*10+ch-\'0\'); \\
  }while(0)

以上是关于奇技淫巧:NOIP的读入优化的主要内容,如果未能解决你的问题,请参考以下文章

关于读入优化的最终分析

Noip前的大抱佛脚----奇技淫巧

NOIP模拟赛乱搞AC奇技淫巧乘法原理回文串计数

Noip前的大抱佛脚----文章索引

洛谷 1967 NOIP2013 货车运输

奇技淫巧之 编译优化