• 动态规划——最长上升子序列模型


    最长上升子序列

    给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

    例如:3 1 2 1 8 5 6这个序列的最长递增子序列长度为4(1 2 5 6)。

    输入格式:

    第一行包含整数 N ;第二行包含 N 个整数,表示完整序列。

    输出格式:

    输出一个整数,表示最大长度。

    数据范围:

    1 <= N <= 1000 ,-1e9 < 序列元素 <1e9 。

    思路:

    假设序列起始位置为 1 ,集合 k 表示以 k 位置为结尾的上升子序列的集合,f[k] 为 k 集合中的上升子序列中的最长的上升子序列的长度;

    由于以 k 位置结尾的上升子序列一定是由以 1 ~ k-1 位置结尾的上升子序列连接得到,故 k 集合可以划分为1、2……,k -1(1表示以 1 位置为结尾的上升子序列的集合,2 表示以 2 位置为结尾的上升子序列的集合…… k-1 表示以 k-1 位置为结尾的上升子序列的集合 );

    由上述可知,f[i] = max( f[1] ~ f[i-1]) +1;

    故可以从前往后枚举,每次枚举中查找 1 ~ i-1,若查找到的 f[j] < f[i],则 f[i] = max( f[i] , f[j] + 1 );

    初始状态 f[i] = 1;

    时间复杂度为O(n^2)。

    代码模板如下:

    1. #include
    2. using namespace std;
    3. const int N = 1010;
    4. int f[N], a[N];
    5. int n, res = 1;
    6. int main() {
    7. cin >> n;
    8. for (int i = 1; i <= n; i++) {
    9. cin >> a[i];
    10. f[i] = 1;
    11. for (int j = 1; j < i; j++)
    12. if (a[j] < a[i]) {
    13. f[i] = max(f[i], f[j] + 1);
    14. res = max(res, f[i]);
    15. }
    16. }
    17. cout << res;
    18. return 0;
    19. }

    优化:

    时间复杂度可优化至O( nlogn ),步骤如下:

    从前到后遍历序列;

    1. 给定一个单调递增栈;

    2. 如果当前遍历到的元素大于栈顶元素,则将当前元素加入栈顶;

    3. 如果当前元素小于等于栈顶,则在栈中查找第一个大于等于当前元素的元素,用当前元素替换掉找到的元素,二分查找时间复杂度O( logn ); 

    栈的大小就是最长上升子序列的长度。

    二分算法:二分算法_二分排序算法_如何何何的博客-CSDN博客

    代码模板如下:

    1. #include
    2. using namespace std;
    3. const int N=1e5;
    4. int q[N],g[N];
    5. int n=1,res;
    6. void check(int x){
    7. //找到第一个大于等于q[x]的
    8. int l=0,r=res,mid;
    9. while(l
    10. mid=(l+r)>>1;
    11. if(g[mid]>=q[x])r=mid;
    12. else l=mid+1;
    13. }
    14. g[l]=q[x];
    15. }
    16. int main(){
    17. cin>>n;
    18. for(int i=1;i<=n;i++)cin>>q[i];
    19. //while(cin>>a[n++]);
    20. //n--;
    21. g[0]=q[1];
    22. for(int i=2;i<=n;i++){
    23. if(q[i]>g[res])g[++res]=q[i];
    24. else check(i);
    25. }
    26. //for(int i=0;i
    27. cout<1;
    28. }

    例题1 . 怪盗基德的滑翔翼:

    怪盗基德的滑翔翼 - C语言网 (dotcpp.com)

    一个方向的最长下降子序列等于反方向的最长上升子序列;

    求出从前往后和从后往前的最长上升子序列即可。

    AC代码如下:

    1. #include
    2. using namespace std;
    3. const int N=110;
    4. int f_l[N],f_r[N];//往左和往右的最长上升子序列
    5. int a[N];
    6. int main(){
    7. int T;
    8. cin>>T;
    9. while(T--){
    10. int n;
    11. cin>>n;
    12. for(int i=0;i>a[i];
    13. int res=0;
    14. //正向最长上升子序列
    15. for(int i=0;i
    16. f_r[i]=1;
    17. for(int j=0;j
    18. if(a[j]max(f_r[i],f_r[j]+1);
    19. res=max(res,f_r[i]);
    20. }
    21. //反向最长上升子序列
    22. for(int i=n-1;i>-1;i--){
    23. f_l[i]=1;
    24. for(int j=n-1;j>i;j--)
    25. if(a[j]max(f_l[i],f_l[j]+1);
    26. res=max(res,f_l[i]);
    27. }
    28. cout<
    29. }
    30. return 0;
    31. }

    例题2 . 登山:

    登山 - C语言网 (dotcpp.com)

    假设 f[i] 为以 i 号山为最高山时,能游览的山的最大数量;

    f[i] 为以 i 点的山为终点左侧序列的最长上升子序列的长度,加上以 i 点的山为起点右侧最长下降子序列的长度,再减去 1 ;

    求最长下降子序列即反向求最长上升子序列。

    AC代码如下:

    1. #include
    2. using namespace std;
    3. const int N=1010;
    4. int f_l[N],f_r[N];//往左和往右的最长上升子序列
    5. int a[N];
    6. int main(){
    7. int n;
    8. cin>>n;
    9. for(int i=0;i>a[i];
    10. //正向最长上升子序列
    11. for(int i=0;i
    12. f_r[i]=1;
    13. for(int j=0;j
    14. if(a[j]max(f_r[i],f_r[j]+1);
    15. }
    16. //反向最长上升子序列
    17. for(int i=n-1;i>-1;i--){
    18. f_l[i]=1;
    19. for(int j=n-1;j>i;j--)
    20. if(a[j]max(f_l[i],f_l[j]+1);
    21. }
    22. int res=0;
    23. for(int i=0;imax(res,f_l[i]+f_r[i]-1);
    24. cout<
    25. return 0;
    26. }
  • 相关阅读:
    npm 安装第三方字体库
    steam deck科普、上手教程及模拟器配置指南
    JavaScript+css实现的动态生成3D树效果html页面前端源码
    Windows系统提示“ping”不是内部或外部命令,也不是可运行的程序或批处理文件解决办法
    flex1时内容溢出
    BFV同态加密方案初步学习
    mac idea 常用快捷键
    【修复版】2023新版塔罗 算八字测运易理风水 取名 源码平台 搭建教程
    D. Yet Another Sorting Problem
    ARMv8常用寄存器记录
  • 原文地址:https://blog.csdn.net/weixin_56265979/article/details/128159537