NOIP 2012 Senior 5

来源:互联网 发布:sql mdf是什么文件 编辑:程序博客网 时间:2024/06/05 11:50

思路1

​ 看到这个,太像线段树了。所以一开始我就用线段树来写,可惜我对数据规模不敏感,硬生生的把106想成了100000,于是就有了尴尬的五十分。本来线段树是能够应对100000个数和100000个查询的,但是若要使用线段树,则需要先减去对应区间,再查询对应区间,相当于查询了200000次,所以并不能拿到想象中的70分。

​ 这个思路的具体细节就不说了,应该都懂。时间复杂度为O((n+m)log2n)log2n大约为400。

思路2

​ 数据规模是1000000,想到了什么?我们需要用一个O(logn)的算法,它就是二分。由于教室数量随着订单的增多是非严格单调递减的,因此二分可行。现在的问题就是check函数了。

​ 再建一棵线段树?这样对时间复杂度的优化毫无作用,在最坏情况下进行的操作比思路1还要多。有没有更好的办法呢?

​ 这里介绍一种新的算法:前缀和。线段树是一种在线算法,而前缀和的功能是给一个数组的多个区间加上相同的数,最后得到一个结果数组,它是一种离线算法。它使check函数的时间复杂度降为O(m+n)

​ 如何使用呢?首先,它一开始必须在一个空数组Temp(各值为0)上进行操作。每有一个操作(L, R, V)(在[L,R]的区间中每个数加上V),它就让Temp[L]+=vTemp[R+1]-=v。在所有操作进行完后,它使用递推公式Temp[i]+=Temp[i-1]进行计算,得到的就是Δ原数组。例如:

Origin 1 2 3 4 5 Temp 0 0 0 0 0 (2,4,5) 0 5 0 0 -5 (1,4,2) 2 5 0 0 -7 Temp 2 7 7 7 0 Origin 3 9 10 11 5

​ 就有了check函数:

int temp[maxn];bool check(int s){    //Note:离线前缀和算法:    memset(temp, 0, sizeof(int) * (n + 1));    for (int i = 1; i <= s; i++)    {        temp[from[i]] += cost[i];        temp[to[i] + 1] -= cost[i];    }    for (int i = 1; i <= n; i++)    {        temp[i] += temp[i - 1];    }    for (int i = 1; i <= n; i++)    {        temp[i] = start[i] - temp[i];        if (temp[i] < 0) return false;    }    return true;}

​ 经过优化,整个程序的时间复杂度为O((n+m)logm)

参考代码

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>using std::cin;using std::cout;using std::endl;#define FOR(x, f, t) for(int x = (f); x <= (t); x++)inline int readIn(){    int a;    scanf("%d", &a);    return a;}const int maxn = 1000005;int n, m;int start[maxn];int cost[maxn];int from[maxn];int to[maxn];int temp[maxn];bool check(int s){    //Note:离线前缀和算法:    memset(temp, 0, sizeof(int) * (n + 1));    for (int i = 1; i <= s; i++)    {        temp[from[i]] += cost[i];        temp[to[i] + 1] -= cost[i];    }    for (int i = 1; i <= n; i++)    {        temp[i] += temp[i - 1];    }    for (int i = 1; i <= n; i++)    {        temp[i] = start[i] - temp[i];        if (temp[i] < 0) return false;    }    return true;}void run(){    n = readIn();    m = readIn();    FOR(i, 1, n)    {        start[i] = readIn();    }    FOR(i, 1, m)    {        cost[i] = readIn();        from[i] = readIn();        to[i] = readIn();    }    int left = 1, right = n + 1;    while (right - left > 1)    {        int mid = (left + right) >> 1;        if (check(mid))        {            left = mid;        }        else        {            right = mid;        }    }    if (check(left)) left++;    if (left != n + 1)    {        printf("-1\n%d\n", left);    }    else    {        printf("0\n");    }}int main(){    run();    return 0;}

​ 看清数据规模,根据数据规模推出时间复杂度并想出对应算法,多积累实用数据结构,就不会少得这50分了。