NOIP 2013 Senior 5

来源:互联网 发布:学唱简谱软件 编辑:程序博客网 时间:2024/06/15 02:59

Description

花匠栋栋种了一排花,每株花都有自己的高度。花儿越长越大,也越来越挤。栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希望剩下的花排列得比较别致。

具体而言,栋栋的花的高度可以看成一列整数h1,h2,,hn。设当一部分花被移走后,剩下的花的高度依次为g1,g2,,gm,则栋栋希望下面两个条件中至少有一个满足:

条件 A:对于1im/2,有g2i>g2i1,同时对于所有的1i<m/2,有g2i>g2i+1

条件 B:对于1im/2,有g2i<g2i1,同时对于1i<m/2,有g2i<g2i+1

注意上面两个条件在m = 1时同时满足,当m > 1时最多有一个能满足。

请问,栋栋最多能将多少株花留在原地。

Input

输入的第一行包含一个整数 n,表示开始时花的株数。

第二行包含 n 个整数,依次为h1,h2,,hn,表示每株花的高度。

Output

输出一行,包含一个整数 m,表示最多能留在原地的花的株数。

Sample Input

5

5 3 2 1 2

Sample Output

3

Data Constraint

对于 20%的数据,n ≤ 10;

对于 30%的数据,n ≤ 25;

对于 70%的数据,n ≤ 1000,0 ≤ hi ≤ 1000;

对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤ hi ≤ 1,000,000,所有的hi随机生成,所有随机数服从某区间内的均匀分布。


思路 1
可以用类似于最长xx子序列的思想来做,时间复杂度为O(n2)

    std::vector<INT> height(n + 1);    std::vector<INT> fA(n + 1, 1);    std::vector<INT> fB(n + 1, 1);    for (int i = 1; i <= n; i++)    {        height[i] = readIn();        for (int j = 1; j < i; j++)        {            if (fA[j] & 1)            {                if (height[i] > height[j])                    fA[i] = std::max(fA[i], fA[j] + 1);            }            else            {                if (height[i] < height[j])                    fA[i] = std::max(fA[i], fA[j] + 1);            }            ans = std::max(ans, fA[i]);            if (fB[j] & 1)            {                if (height[i] < height[j])                    fB[i] = std::max(fB[i], fB[j] + 1);            }            else            {                if (height[i] > height[j])                    fB[i] = std::max(fB[i], fB[j] + 1);            }            ans = std::max(ans, fB[i]);        }    }

思路 2

可以用线段树加速DP:

    SegTre stA1; //A为奇数的    SegTre stA2; //A为偶数的    SegTre stB1; //B为奇数的    SegTre stB2; //B为偶数的    for (int i = 1; i <= n; i++)    {        INT height = readIn() + 2;        INT valA1 = stA1.query(1, height - 1) + 1; //偶数        INT valA2 = stA2.query(height + 1) + 1; //奇数        INT valB1 = stB1.query(height + 1) + 1; //偶数        INT valB2 = stB2.query(1, height - 1) + 1; //奇数        if (!(valA1 & 1))        {            ans = std::max(ans, valA1);            stA2.set(height, valA1);        }        if (valA2 & 1)        {            ans = std::max(ans, valA2);            stA1.set(height, valA2);        }        if (!(valB1 & 1))        {            ans = std::max(ans, valB1);            stB2.set(height, valB1);        }        if (valB2 & 1)        {            ans = std::max(ans, valB2);            stB1.set(height, valB2);        }    }

然而以上两种思路的状态转移方程分了A,B两种情况,要判断4次,太渣了,以至于使用线段树也只能过9个点。

应该采用这个状态转移方程:(f[i]代表最高点,g[i]代表较低点)

f[i]=max1j<iaj<ai{g[j]+1}

g[i]=max1j<iaj>ai{f[j]+1}

因为A,B其实是一种情况,要把它们看做锯齿。

void SuperDP(){    SegTre f; //较大    SegTre g; //较小    for(int i = 1; i <= n; i++)    {        INT height = readIn() + 2;        INT valG = f.query(height + 1) + 1;        INT valF = g.query(1, height - 1) + 1;        ans = std::max(ans, std::max(valF, valG));        f.set(height, valF);        g.set(height, valG);    }}

思路 2
那为什么不直接找锯齿呢。。。或者说,找所有在单调上升和单调下降之间的点。
首先,我们要先把连续的相同高度的花删掉。然后我们从头到尾扫过去,遇到了拐点就让答案+1,最后算上两个端点就好了。
动归固然跪,分析大法好。

参考代码

#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>#include <bitset>using std::cin;using std::cout;using std::endl;typedef long long INT;inline INT readIn(){    bool minus = false;    INT a = 0;    char ch = getchar();    while(!(ch == '-' || ch >= '0' && ch <= '9')) ch = getchar();    if(ch == '-')    {        minus = true;        ch = getchar();    }    while(ch >= '0' && ch <= '9')    {        a *= 10;        a += ch;        a -= '0';        ch = getchar();    }    if(minus) a = -a;    return a;}const INT maxn = 100005;INT n;INT N;INT height[maxn];INT ans;void run(){    n = readIn();    ans = 1;    INT pre = -1;    for(int i = 1; i <= n; i++)    {        INT input = readIn();        if(input == pre) continue;        height[++N] = input;        pre = input;    }    if(N >= 2) ans++; //如果长度大于等于2的话就有两个端点    for(int i = 2; i <= N - 1; i++)    {        if(height[i - 1] < height[i] && height[i] > height[i + 1]) ans++;        else if(height[i - 1] > height[i] && height[i] < height[i + 1]) ans++;    }    cout << ans << endl;}int main(){    run();    return 0;}