这种奇怪的I / O方法如何工作?

时间:2017-06-04 13:03:02

标签: c++ io

在C ++中输入输出时我只使用了scanf / printf和cin / cout。现在我最近遇到了this代码,以一种奇怪的方式进行I / O.

另请注意,此I / O方法导致代码运行速度极快,因为此代码使用与大多数其他代码几乎相同的算法,但它在更短的时间内执行。为什么这个I / O如此之快以及这一般如何工作?

编辑:代码

 #include <bits/stdtr1c++.h>

 #define MAXN 200010
 #define MAXQ 200010
 #define MAXV 1000010
 #define clr(ar) memset(ar, 0, sizeof(ar))
 #define read() freopen("lol.txt", "r", stdin)

 using namespace std;

 const int block_size = 633;

 long long res, out[MAXQ]; int n, q, ar[MAXN], val[MAXN], freq[MAXV];

 namespace fastio{
     int ptr, ye;
     char temp[25], str[8333667], out[8333669];

     void init(){
         ptr = 0, ye = 0;
         fread(str, 1, 8333667, stdin);
     }

     inline int number(){
         int i, j, val = 0;

         while (str[ptr] < 45 || str[ptr] > 57) ptr++;
         while (str[ptr] > 47 && str[ptr] < 58) val = (val * 10) + (str[ptr++] - 48);
         return val;
     }

     inline void convert(long long x){
         int i, d = 0;

         for (; ;){
             temp[++d] = (x % 10) + 48;
             x /= 10;
             if (!x) break;
         }
         for (i = d; i; i--) out[ye++] = temp[i];
         out[ye++] = 10;
     }

     inline void print(){
         fwrite(out, 1, ye, stdout);
     } }

 struct query{
     int l, r, d, i;

     inline query() {}
     inline query(int a, int b, int c){
         i = c;
         l = a, r = b, d = l / block_size;
     }

     inline bool operator < (const query& other) const{
         if (d != other.d) return (d < other.d);
         return ((d & 1) ? (r < other.r) : (r > other.r));
     } } Q[MAXQ];

 void compress(int n, int* in, int* out){
     unordered_map <int, int> mp;
     for (int i = 0; i < n; i++) out[i] = mp.emplace(in[i], mp.size()).first->second; }

 inline void insert(int i){
     res += (long long)val[i] * (1 + 2 * freq[ar[i]]++); }

 inline void erase(int i){
     res -= (long long)val[i] * (1 + 2 * --freq[ar[i]]); }

 inline void run(){
     sort(Q, Q + q);
     int i, l, r, a = 0, b = 0;

     for (res = 0, i = 0; i < q; i++){
         l = Q[i].l, r = Q[i].r;
         while (a > l) insert(--a);
         while (b <= r) insert(b++);
         while (a < l) erase(a++);
         while (b > (r + 1)) erase(--b);
         out[Q[i].i] = res;
     }
     for (i = 0; i < q; i++) fastio::convert(out[i]); }

 int main(){
     fastio::init();
     int n, i, j, k, a, b;

     n = fastio::number();
     q = fastio::number();
     for (i = 0; i < n; i++) val[i] = fastio::number();
     compress(n, val, ar);

     for (i = 0; i < q; i++){
         a = fastio::number();
         b = fastio::number();
         Q[i] = query(a - 1, b - 1, i);
     }

     run();
     fastio::print();
     return 0; }

1 个答案:

答案 0 :(得分:3)

此解决方案http://codeforces.com/contest/86/submission/22526466(624毫秒,32 MB RAM使用)使用单个fread和手动解析内存中的数字(因此它使用更多内存);许多其他解决方案速度较慢,使用scanfhttp://codeforces.com/contest/86/submission/27561563 1620 ms 9MB)或C ++ iostream cinhttp://codeforces.com/contest/86/submission/27558562 3118 ms,15 MB)。并非所有解决方案的差异都来自输入输出和解析(解决方法方法也存在差异),但有些是。

 fread(str, 1, 8333667, stdin);

此代码使用单fread libcall读取最多8MB,这是完整文件。该文件最多可包含2(n,t)+ 200000(a_i)+ 2 * 200000(l,r)6/7位数字,带或不带换行符或由一个(?)空格分隔,因此大约8个字符数字最大值(数字为6或7,允许1000000,1个空格或\n);最大输入文件大小类似于0.6 M * 8字节= ~5 MB。

 inline int number(){
     int i, j, val = 0;

     while (str[ptr] < 45 || str[ptr] > 57) ptr++;
     while (str[ptr] > 47 && str[ptr] < 58) val = (val * 10) + (str[ptr++] - 48);
     return val;
 }

然后代码使用解析十进制int数字的手动代码。根据ascii表,48 {57的http://www.asciitable.com/十进制代码是十进制数字(第二次循环):'0'...'9',我们可以从字母代码中减去48得到数字;将部分读取val乘以10并添加当前数字。第一个while循环中的chr<45 || chr > 57听起来像是从输入中跳过非数字。这是不正确的,因为此代码不会解析代码45,46,47 = '-', '.', '/',并且在读取这些字符后没有任何数字。

 n = fastio::number();
 q = fastio::number();
 for (i = 0; i < n; i++) val[i] = fastio::number();

 for (i = 0; i < q; i++){
     a = fastio::number();
     b = fastio::number();

实际阅读使用此fastio::number()方法;和其他解决方案使用循环中调用scanf或iostream operator <<

for (int i = 0; i < N; i++) {
    scanf("%d", &(arr[i]));
    add(arr[i]);
}

for (int i = 1; i <= n; ++i)
    cin >> a[i];

这两种方法都更通用,但它们会进行库调用,它会从内部缓冲区读取一些字符(如4KB)或调用OS系统调用来进行缓冲区重新填充,并且每个函数都会执行许多检查并报告错误:对于每个数字输入scanf将重新分析相同的format string of first argument,并将完成POSIX http://pubs.opengroup.org/onlinepubs/7908799/xsh/fscanf.html中描述的所有逻辑以及所有错误检查。 C ++ iostream没有格式字符串,但它更具通用性:https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/istream.tcc#L156'operator>>(int& __n)'。

因此,标准库函数内部逻辑更多,调用更多,分支更多;它们更普遍,更安全,应该用于现实世界的编程。这个“体育节目”比赛允许用户使用足够快的标准库函数来解决任务,如果你能想象算法的话。作者或任务需要编写具有标准i / o函数的多个解决方案,以检查任务的时间限制是否正确并且任务可以解决。 (对于i / o,TopCoder系统更好,你不会实现i / o,数据已经在某些语言结构/集合中传递给你的函数)。

有时sport programming中的任务对内存有严格限制:输入文件比允许的内存使用量大几倍,程序员无法将整个文件读入内存。例如:从输入文件中获取20万个单个非常长的数字,并加1,内存限制为2 MB;你不能正向读取文件中的完整输入数字;在向后方向上正确读取是非常困难的;你只需要忘记标准的加法方法(柱状加法)并用状态构建FSM(有限状态机),计算9 s的序列。