洛谷\(P1970\)git
花匠栋栋种了一排花,每株花都有本身的高度。花儿越长越大,也愈来愈挤。栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋但愿剩下的花排列得比较别致。算法
具体而言,栋栋的花的高度能够当作一列整数\(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}\)spa
注意上面两个条件在\(m=1\)时同时知足,当\(m > 1\)时最多有一个能知足。code
请问,栋栋最多能将多少株花留在原地。get
第一行包含一个整数\(n\),表示开始时花的株数。it
第二行包含\(n\)个整数,依次为\(h_1,h_2,...,h_n\),表示每株花的高度。io
一个整数\(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\)上面的方程就结束了
#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; }
贪心大法真的好诶(逃