【破烂集】 输入问题

说实话这个问题之前一直没有意识到,后来才发现这门学问是如此高深,本着精益求精的态度,有些时间空间的高度优化还是必要的,有时候可能就跟炉石里的伏笔似的,所以即使在做最简单的问题的时候其实也能有一些不错的想法。由于这个问题十分高深,所以我也没有办法绝对说明,也是慢慢研究中的,但我会不断更新,把我所有的研究成果汇总整理到这里,war3里的女巫说过“help me , help you”我想还是有一定道理的。

事先说明,很多成果都有我借鉴的成分,是很多聚聚们优秀智慧的结晶,再次膜拜他们。

先用一道简单题来作为开头吧——xdoj1024简单逆序对

来看看两个代码
A

# include <cstdio>
# include <cstring>

const int MAX_N = 2e6;
const int Mod = 1e9 + 7;

char s[MAX_N];
int box[10];
int T, N;

int main()
{
    scanf("%d", &T);

    while(T--)
    {
        memset(box , 0 , sizeof(box));

        scanf("%d", &N);

        long long ans = 0;
        while(N--)
        {
            int t;
            scanf("%d", &t);
            box[t]++;

            while(t < 9)
                ans += box[++t];
        }

        printf("%lld\n", ans % Mod);
    }

    return 0;
}

B

# include <cstdio>
# include <cstring>

const int MAX_N = 2e6;
const int Mod = 1e9 + 7;

char s[MAX_N];
int box[10];
int T, N;

int main()
{
    scanf("%d", &T);

    while(T--)
    {
        memset(box , 0 , sizeof(box));

        scanf("%d\n", &N);

        char * p = gets(s);
        long long ans = 0;
        while(N--)
        {
            int t = * p - 48;
            box[t]++;

            while(t < 9)
                ans += box[++t];

            p += 2;
        }

        printf("%lld\n", ans % Mod);
    }

    return 0;
}

可能很多认觉得这两个代码没有什么特别不同的地方,但是结果却是显著的。可以看一看
这里写图片描述
可以看到空间一栏没有区别,但是时间一栏却相差了近6倍,那么很多人可能好奇这是在打印了多少组数的结果呢,我可以很确定的告诉你,这道题的测试数据只有1e6左右,也就是说在一行1e6的情况下常规的scanf比用字符串输入一行的gets慢了280ms,这是多么恐怖的一个数字,所以这也让我有了研究下去的动力。我们接下来继续深层次的看看吧。

我暂时先把这个方法叫做gets大法,这道题的确有一些特殊的成分,比如数字都是一位的,并且每个数字之间都保证只有一个空格,所以为了进一步研究这个方法的普适性,我又用这个方法提交了1099简单的问题,这道题的输入格式和1024很相似,但是首先每个数字有好几位,并且数字之间有两个空格,所以需要一定的改动。这是一道尺取法的题,一般来说最优化的普通方法大约是250ms的时间,那么用了gets优化之后的结果呢?我尝试着写了一个,发现平均只需要50ms的时间,最快达到了40ms最慢也不超过60,还是很给力的。所以这个方法看起来效果是很不错的。不过一个小问题是我们需要研究清楚题目的格式,包括字母大小写以及正负的问题,我打算再尝试一些从而做出一个比较通用的输入模板,这样在处理大输入问题的时候能够变得更好。

顺便把1099的输入优化后的代码贴出来吧(并不一定是最好的)

#include<cstdio>

typedef long long ll;

const int MAX_C = 1e7;

char s[MAX_C];
int a[100001];
int N, K, T;

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d %d\n", &N, &K);

        gets(s);

        int i, j;

        a[0] = i = 0;
        bool f = 1;
        for(j = 0 ; s[j] ; j++)
        {
            if(s[j] > 57 || s[j] < 48)
            {
                if(!f)
                    a[++i] = 0;
                f = 1;
            }
            else
            {
                a[i] = (10 * a[i]) + s[j] - 48;
                f = 0;
            }
        }

        int sum = 0, l = 0;
        ll ans = 0;
        for(i = 0 ; i < N ; i++)
        {
            sum += a[i];
            while(sum > K)
                sum -= a[l++];
            ans += i + 1 - l;
        }

        printf("%lld\n",ans);
    }

    return 0;
}

2018、5、26
另外说一说这个gets()的一个很迷的问题,就是吃回车的问题,因为scanf是输入到空格或回车结束,但是不吃他们的,所以有可能会导致那个回车被gets吃掉从而出错,比较常规的做法是加一个getchar,不过这个依然比较迷,因为我也遇到过什么都不加却对了,加了getchar却错了的情况,还有都错的情况,所以在看到了在scanf后面加\n这个方法之后,感觉还是这个方法最稳,因为的确遇到了加不加getchar都错但是加了\n之后却对了的情况。所以用gets前这个回车的问题一定要考虑好。

再说说经典的输入方式,和gets基本没有区别,只不过是用getchar来进行捕获,不过的确getchar这个要慢一些,但是好处是省内存,用gets的话大约需要10倍的MAX_N才能保险,不过大部分情况是没问题的。另外,就算用经典的方法其实也有更快的手段,就是用getchar_unlocked代替getchar这个方法可以说是简单粗暴地提速一倍左右,利益类比于cin的呢个关掉什么什么东西(我也不用cin,所以也记不清了),理论上来说是因为单线程输入虽然危险,但是快,所以在acm领域基本是没有问题的,gets好像就是单线程的(用了好像会被oj警告),getchar_unlocked也是,所以再用它优化之后效果只比gets大法慢一点点。

就拿1307的测试结果为例 时间
简单gets模板 34——56
复杂gets模板(适合更多情况) 36——56
经典getchar 100左右
优化的getchar 60左右

另外贴上几个模板以供参考

简单gets

/*
注意事项
1因为有gets,之前如果用scanf的话结尾加/n
2数组需要留有富裕,否则可能答案出错
3数字之间间隔必须是一个非数字字符
4没有负数
*/

char s[10 * MAX_N];

void scan(int * a)
{
    gets(s);

    int i, j;

    a[0] = j = 0;
    for(i = 0 ; s[i] ; i++)
    {
        if(s[i] > 57 || s[i] < 48)
            a[++j] = 0;
        else
            a[j] = (a[j] * 10) + s[i] - 48;
    }
}

复杂gets

/*
1时间上和简单版一致(做都可以适用的问题时)
2可以处理数字间多间隔情况
3可以处理负数
4如果符合要求建议使用简单版
注意!!!
5上一个scanf别忘了在末尾加/n
6数组需要留有富裕,否则可能答案出错
*/

char s[10 * MAX_N];

void scan(int * a)
{
    gets(s);

    int i, j;

    i = j = 0;
    while(s[i])
    {
        a[j] = 0;
        bool f = 0;
        while(s[i] && (s[i] > 57 || s[i] < 48))
            if(s[i++] == '-')
                f = 1;

        while(s[i] <= 57 && s[i] >= 48)
            a[j] = (a[j] * 10) + s[i++] - 48;

        if(f)
            a[j] *= -1;

        j++;
    }
}

经典

# define get getchar_unlocked

void scan(int n , int * a)
{
    char c;
    int i = 0;

    while(i < n)
    {
        bool f = 0;
        a[i] = 0;

        c = get();
        while(c > 57 && c < 48)
        {
            if(c == '-')
                f = 1;

            c = get();
        }

        while(c >= 48 && c <= 57)
        {
            a[i] = 10 * a[i] + c - 48;
            c = get();
        }

        if(f)
            a[i] *= -1;

        i++;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值