DP练习题——洛谷P1970花匠

题目描述:

洛谷\(P1970\)

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

具体而言,栋栋的花的高度可以看成一列整数\(h_1,h_2,...,h_n\)。设当一部分花被移走后,剩下的花的高度依次为\(g_1,g_2,...,g_m\),则栋栋希望下面两个条件中至少有一个满足:

条件 \(A\):对于所有\(g_{2i}>g_{2i-1},g_{2i}>g_{2i+1}\)

条件 \(B\):对于所有\(g_{2i}<g_{2i-1},g_{2i}<g_{2i+1}\)

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

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

输入输出格式:

输入格式:

第一行包含一个整数\(n\),表示开始时花的株数。

第二行包含\(n\)个整数,依次为\(h_1,h_2,...,h_n\),表示每株花的高度。

输出格式:

一个整数\(m\),表示最多能留在原地的花的株数。

输入输出样例:

输入样例:

5
5 3 2 1 2

输出样例:

3

题目分析:

​ 简单理解一下题意:

大体就是说要求留在原地的花满足:序号为\(2\)的倍数的花是左右两盆花中最高或者是最矮的

解法一:

思路:

​ 既然是\(DP\)的题,那么我们先考虑贪心(其实是为了纪念\(lz\)):

说句闲话:学习\(DP\)的最好方法是什么??

贪心!!!

好了好了,说正经的。

不过还是要吐槽这道题的算法标签,竟然没人评贪心(也不知道\(lz\)评了没有)

我们可以发现,这道题就是对一个有波动的函数进行简化,我们可以举两个例子,一个是样例:

我们可以把这个图像看做一个具有两个单调区间的函数图像,我们只能去单调区间两端的点构成新的序列,也就是说我们最多能保留三盆花

另一个是从讨论版里找来的神仙数据:

有了上面的分析,我们可以很轻易地找出\(3\)个单调区间,也就是说最多有\(4\)盆花能够被留下

算法:

​ 根据两组数据,我们可以得到这样一个算法:

扫描所有花的高度,从而得出增减区间的个数\(w\),输出\(w+1\)

代码实现:

#include<cstdio>
#include<iostream>

using namespace std;

inline int read()
{
    int F=1,num=0;
    char c=getchar();
    while(!isdigit(c)){if(c=='-') F=-1; c=getchar();}
    while(isdigit(c)){num=num*10+c-'0'; c=getchar();}
    return num*F;
}

int h[10000010];
int n;
int ans=1;//考虑到函数的左区间
int direction;

int main()
{
    n=read();
    for(int i=1;i<=n;++i)
        h[i]=read();//读入 
    if(h[2]>=h[1])  direction=1;//1表示增区间,0表示减区间 ,因为前两个是默认都选的,所以在这里加上了等号
    for(int i=1;i<=n;++i)//我知道可以边读入边判断,但是我愿意,你管着吗?? 
    {
        if(i==n)
        {
            ans++;//考虑到函数的右区间,当到了右区间时答案直接加一并且跳出循环
            break;
        }
        if(h[i+1]>h[i])
        {
            if(direction==0)//如果突然进入到增区间,就标记为增区间并且答案加一
            {
                ans++;
                direction=1;
            }
        }
        if(h[i+1]<h[i])
        {
            if(direction==1)//如果突然进入到减区间,就标记为减区间并且答案加一
            {
                ans++;
                direction=0;
            }
        }
    }
    printf("%d",ans);//输出答案
    return 0;
}

解法二:

思路:

当然是已经哭晕在厕所的\(DP\)

\(dp[i][0]\)表示在\(i\)处上升时的最大盆数,\(dp[i][1]\)表示在\(i\)处下降时的最大盆数

直接得到状态转移方程

\(if(a[i]>a[i-1])\space\space\space dp[i][0]=dp[i-1][1]+1;\\if(a[i]<a[i-1])\space\space\space dp[i][1]=dp[i-1][0]+1;\\dp[i][0]=max(dp[i][0],dp[i-1][0]);\\dp[i][1]=max(dp[i][1],dp[i-1][1]);\)

解释一下:

\(a[i]\)是上升的时候,将上升时的最大盆数更新为上一盆下降时的最大盆数+\(1\),并且用这两种状态的最大值来维护\(dp[i][0]\),表示满足条件\(B\)

\(a[i]\)是下降的时候,将下降时的最大盆数更新为上一盆上升时的最大盆数+\(1\),并且用这两种状态的最小值来维护\(dp[i][1]\),表示满足条件\(A\)

​ 如果还不能理解就画个图,手推一下233~~

算法:

​ 就是将\(dp[1][1]\)\(dp[1][0]\)都更新为\(1\),然后从\(2\)开始跑一遍循环,\(CV\)上面的方程就结束了

Talk is cheap,show me the code:

#include<cstdio>
#include<iostream>

using namespace std;

const int maxn=101000;
int dp[maxn][2],a[maxn];
int n;

inline int read()
{
    int F=1,num=0;
    char c=getchar();
    while(!isdigit(c)){if(c=='-')   F=-1;   c=getchar();}
    while(isdigit(c)){num=num*10+c-'0';     c=getchar();}
    return F*num;
}

int main()
{
    n=read();
    dp[1][0]=1;dp[1][1]=1;
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=2;i<=n;i++)
    {
        if(a[i]>a[i-1]) dp[i][0]=dp[i-1][1]+1;
        if(a[i]<a[i-1]) dp[i][1]=dp[i-1][0]+1;
        dp[i][0]=max(dp[i][0],dp[i-1][0]);
        dp[i][1]=max(dp[i][1],dp[i-1][1]);
    }
    printf("%d\n",max(dp[n][1],dp[n][0]));
    return 0;
}

结语:

贪心大法真的好诶(逃

猜你喜欢

转载自www.cnblogs.com/juruohqk/p/11130392.html