刷题老超时?那是因为你不会开挂

2022-04-18 15:48:30 浏览数 (1)

作者 | 小K

01

故事起源

不知道有多少人还在坚持着刷题,小K偶尔也会刷几道,以免生疏。如果只是为了面试,学会算法的思想就可以应付所有场景了,但如果是为了打比赛,可能coding的速度也很重要,还记得以前的要求是要能5分钟盲敲快排,bugfree。

但有时有这么一种场景,自己满怀信心coding一道题,一提交直接TLE,这就让人有点头大了,可能因此丢掉100分,这就相当严重了呀。

之前我写了一道线段树的题就遇到了。

02

分析

小K刷过很多题了,所以对这类的问题也见怪不怪了,AC是天意,不AC才是常态,要习惯,哈哈。。。

一般超时就几种原因: 1、算法复杂度太高,如果数据规模是1w,那O(n^2)的算法肯定超时。 2、某些特殊场景导致死循环,这种比较难排查,可以生成一些样例跑一跑,说不定有惊喜哦。 3、语言的特性,比如java确实比c慢,但一般平台对不同语言的超时限制不一样。这也是我一直用c 来写算法的原因之一。 4、递归太深,这种是指数级的,数据大于2位数估计就会超时,所以一看规模就知道能不能用递归。 5、动态申请空间,这种也会增加耗时,所以堆、栈、树这些数据结构,如果内存允许,一般比赛都是直接用数组来模拟会更保险,别new。 6、输入、输出,这个容易忽略,但不同的方式还真的差别很大。

这次我的实现里面可能会比较耗时的点:一是用了动态申请内存,二是用了cin流读入。很快就定位了原因,还真是这个读入方式导致的。

其实在读入方式上还是有点技巧的,不知你有没有听过一种方式,叫快读,俗称开挂。

03

读入方式

3.1

cin

c 常用的方式是用cincout进行输入输出,比如像这样。

代码语言:javascript复制
cin >> a;

简单场景用cincout还是挺方便的,但如果输出的格式复杂一点就会很恶心了,比如这样。

代码语言:javascript复制
cout << "t=" << setiosflags(ios::fixed) << setprecision(2) << setw(10) << setfill('0') << t << endl;

如果用printf很简单就可以实现相同的效果。

代码语言:javascript复制
printf("t=0.2lfn", t);

就问你,有没有感受到来自c 的压迫感,为啥要这样设计来着。。。

然而比这更恼火的就是cin的读入效率了,是真的低,后面我会做测试。至于为啥低,先看一下它的源码。

这一堆是啥,别问我,我也不知道,一看我直接退出了,哈哈,从来不去卷源码。

还记得以前面试的时候,当面试官问我hashmap的底层数据结构是啥,如何实现的?通过临时看的几篇零散的博客,和多年数据结构和算法的经验,我早就脑补好了一切。

“hashmap啊,这个我研究过,是这样来实现的,不就是数组 链表嘛...”

“你们项目spring是咋配置的?” “哦,我没写过java,不过我搭过demo,不知你说的配置是啥?”

3.2

scanf

那不用c 的流,自然就是用c里面的scanf,这个确实快很多了。

代码语言:javascript复制
scanf("%d", &a);

3.3

getchar

还有一种更快的方式,就是常说的快读,用getchar按字符读取,再转为数字。

代码语言:javascript复制
inline int fast_read() {
    int x = 0;
    char ch = 0;
    bool minus = false;
    while (ch < '0' || ch > '9') minus |= ch == '-', ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3)   (x << 1)   (ch ^ 48), ch = getchar();
    return minus ? -x : x;
}
void main() {
    int t;
    t = fast_read();
}

位移等价乘10,异或是相同为0不同为1,等价减48。至于为啥要用位运算,是因为它快啊。

3.4

fread

这就是所谓的开挂读。即先读入内存中,再直接从内存里取。

代码语言:javascript复制
#define gc() (p1 == p2 && (p2 = (p1 = buf)   fread(buf,1,1<<20,stdin), p1 == p2) ? 0 : *p1  )
char buf[1 << 20], *p1 = buf, *p2 = buf;
template<typename T>
inline void fread_int(T &x) {
    x = 0;
    char c = 0;
    bool minus = false;
    while (c < '0' || c > '9') minus |= c == '-', c = gc();
    while (c >= '0' && c <= '9') x = (x << 3)   (x << 1)   (c ^ 48), c = gc();
    if (minus > 0) x = -x;
}

04

测试

针对上面的4种读入方式,来实际测试一下到底有多大的差别。先生成1kw个随机数。

代码语言:javascript复制
const int N = 10000000;
void data() {
    ofstream fout("a.in");
    srand((int) time(0));
    for (int i = 0; i < N;   i) {
        fout << rand() << endl;
    }
}

再依次用4种方式读入。

代码语言:javascript复制
struct read_function {
    function<void()> engine;
    string name;
};
void process(const read_function &func) {
    using namespace std::chrono;
    steady_clock::time_point start = steady_clock::now();
    func.engine();
    steady_clock::time_point end = steady_clock::now();
    duration<double> time_span = duration_cast<duration<double>>(end - start);
    cout << setw(7) << func.name << " time: " << setiosflags(ios::fixed) << time_span.count() << endl;
}
int main() {
    read_function fun_list[10];
    fun_list[0] = {read4, "fread"};
    fun_list[1] = {read3, "getchar"};
    fun_list[2] = {read2, "scanf"};
    fun_list[3] = {read1, "cin"};
    for (int i = 0; i < 4;   i) {
        freopen("a.in", "r", stdin);
        process(fun_list[i]);
        fclose(stdin);
    }
    return 0;
}

用我的win台式机来测试,配置11700k,32g内存,跑出的结果如下:

代码语言:javascript复制
  fread time: 0.211165
getchar time: 0.909146
  scanf time: 2.756034
    cin time: 3.857858

05

POJ平台测试

再用不同的读入方式通过POJ平台进行提交,也确实存在较大的耗时差异。

06

总结

今天的小技巧在工作中可能用处不大,但在比赛中可就厉害了呀。不过还是不要轻易开挂,万一平台不支持就玩脱了,哈哈。

0 人点赞