奇技淫巧:NOIP的讀入優化

最近看到洛谷上面有一個讀入優化的代碼:git

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讀入優化。spa

而後衆所周知,cin比scanf慢,getchar最快。指針

可是到底差距有多大不少人都不知道。code

因而上個星期我作了一個賊有意思的測試,把scanf、cin、getchar(分爲宏定義函數和內聯函數兩個)分別讀入1000000個數,而後輸出運行時間。blog

getchar的兩個函數貼在這裏:ci

#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)
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息