前言

鉴于本蒟蒻的dp学的实在是一坨答辩,临近lqb开始重新学习一遍dp,在acwing和LIS搏斗两天之后,写一篇总结,加深一下印象。

模板

最长上升子序列

题意与大致思路

题意:

给一个数组,找数组的一个子序列,该序列严格递增且长度最大

状态表示:

dp[i] 表示 以a[i]为结尾的最长上升子序列的长度

状态转移:

对于每个i,遍历1~i,如果找到一个a[j] < a[i],那么ai就可以接到以aj为结尾的LIS后面,此时就可以更新 dp[i] = max(dp[j] + 1 , dp[i]) ,时间复杂度 O (n^2)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int a[maxn],dp[maxn];
int main()
{int n;cin>>n;for(int i=1;i<=n;i++) cin>>a[i],dp[i] = 1; //dp[i]初始化为1,因为显然a[i]本身即为LISint res=0;for(int i = 1; i <= n;i ++){for(int j = 1;j < i;j ++)if(a[j] < a[i])dp[i] = max(dp[i] , dp[j] + 1);res = max(res , dp[i]);  //维护dp[i]的最大值}cout<<res<<endl;return 0;
}

接下来上题

Acwing 1017 - 怪盗基德的滑翔翼

怪盗基德的滑翔翼

Think

该题可以选择向左或者向右选择一条严格递减的序列,往右走就是最长下降子序列,而往左走实际上为最长上升子序列,直接dp并维护最大值即可

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+10;
int a[maxn],dp_up[maxn],dp_down[maxn];
int main()
{int T;cin>>T;while(T--){int n;cin>>n;for(int i=1;i<=n;i++){cin>>a[i];dp_up[i]=1;dp_down[i]=1;}for(int i=1;i<=n;i++)for(int j=0;j<i;j++)if(a[i]>a[j])dp_down[i]=max(dp_down[i],dp_down[j]+1);else if(a[i] < a[j])dp_up[i]=max(dp_up[i],dp_up[j]+1);int res=0;for(int i=1;i<=n;i++) {res=max(res,dp_down[i]);res=max(res,dp_up[i]);}cout<<res<<endl;}return 0;
}

Acwing 482-合唱队列

合唱队列

Think

不难想到把问题转化为找一个以i为终点的最长不下降子序列和一个以i为起点的最长不上升子序列,如何得到以i为起点的LIS呢,只需要反向跑一个最长不下降子序列就可以了

//
//  main.cpp
//  合唱团
//
//  Created by 77777 董昊鹏 on 2022/3/4.
//#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+10;
int a[maxn],dp_up[maxn],dp_down[maxn];
int main()
{int n;cin>>n;for(int i=1;i<=n;i++){cin>>a[i];dp_up[i]=1;dp_down[i]=1;}for(int i=1;i<=n;i++){for(int j=1;j<i;j++)if(a[i]>a[j]) dp_up[i]=max(dp_up[i],dp_up[j]+1);}for(int i=n;i>=1;i--)for(int j=n;j>i;j--)if(a[i]>a[j]) dp_down[i]=max(dp_down[i],dp_down[j]+1);int res=0;for(int i=1;i<=n;i++)res=max(res,dp_up[i]+dp_down[i]-1);cout<<n-res<<endl;
}

Acwing 1012-友好城市

友好城市

Think

该题目要求两边都无交叉,显然对于两边的城市都要保证一个严格递增,按照其中一边排序后,对于另一边跑LIS就可以得到答案。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+10;
pair<int,int> a[maxn];
int dp[maxn];
int main()
{int n;cin>>n;for(int i=1;i<=n;i++) cin>>a[i].first>>a[i].second,dp[i] = 1;sort(a+1,a+1+n);int res=0;for(int i=1;i<=n;i++){for(int j=1;j<i;j++)if(a[j].second < a[i].second)dp[i] = max(dp[j] + 1,dp[i]);res= max(res,dp[i]);}cout<<res<<endl;
}

Acwing 1010-拦截导弹

导弹拦截

Think

LIS的究极经典例题,对于第一问无需多谈,重点在于第二问,可以贪心去做,也可以用到一个偏序集相关的定理,dilworth定理:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。人话就是:如果让你求最少的不上升子序列,这几个序列中包含整个数组的所有元素,即求最大不下降子序列的长度,严格证明可以看tofu佬的:

偏序集,哈斯图与Dilworth定理 - Tofu 的博客 - 洛谷博客 (luogu.org)

所以第二问再跑一个最长不下降子序列即可

#include<bits/stdc++.h>
using namespace std;
int main()
{string s,x;getline(cin,s);stringstream ss(s);int n=0,a[1010],dp[1010];while(ss>>x){a[++n] = stoi(x);dp[n] = 1;}int res=0;for(int i=1;i<=n;i++){for(int j=1;j<i;j++)if(a[i] <= a[j])dp[i] = max(dp[j] + 1,dp[i]);res = max(dp[i],res);}cout<<res<<endl;for(int i=1;i<=n;i++) dp[i] = 1;res=0;for(int i=1;i<=n;i++){for(int j=1;j<i;j++)if(a[j] < a[i])dp[i] = max(dp[j] + 1,dp[i]);res = max(res,dp[i]);}cout<<res<<endl;
}

Acwing  896-最长上升子序列 Ⅱ

最长上升子序列Ⅱ

Think

这次n变为1e5,n^2的算法肯定没法通过,需要进行优化。考虑原本的dp算法,转移的过程是找每个ai接在哪里使得答案最优, 由于n较小可以直接枚举位置并dp取最大值。现在我们来直接讨论这个ai接的位置。

状态表示与状态转移

我们维护一个dp[i],表示长度为i的子序列尾部的值。然后遍历ai,考虑对于每个ai可以接在哪个序列后面,比如两个序列,他的结尾值分别是1和1000,显然接在1000绝对不会比接在1后面更差,因为1比较小,后面还有很多数能接。也就是说结尾的数越大越好,我们可以去找第一个小于等于ai的dp[i],而这个dp是一定单调的,证明如下:

考虑反证:如果res[i] >= res[j] 且i < j,也就是说,更短的子序列有更大的末尾值,这个显而易见是不成立的,比如你在res[j]删除几个元素,使得其长度变为i,此时的末尾值res[k]一定是小于res[j]的,与前提矛盾。

那么我们找第一个小于等于ai的dpi这个过程就可以用二分来解决,总的时间复杂度就可以优化到O(nlogn)。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int a[maxn] , dp[maxn];
int main()
{int n;cin>>n;for(int i=1;i<=n;i++) cin>>a[i];int res=0;dp[++res] = a[1];for(int i=2;i<=n;i++){//cout<<i<<" "<<dp[res]<<endl;if(a[i] > dp[res]) dp[++res] = a[i];else {int pos = lower_bound(dp + 1,dp + res + 1,a[i]) - dp;dp[pos] = a[i];}}cout<<res<<endl;
}

Acwing 187-导弹拦截系统

导弹拦截系统

Think

船新的导弹拦截,在这个题里面,既可以是最长上升也可以是最长下降,我们可以采取上题的思路,用一个up[i]和一个down[i]分别表示每个序列的末尾数。然后我们可以直接按照上一题的贪心思路,即:一直接在第一个小于等于a[i]的序列后面。进行一波搜索剪枝,更多具体细节看代码注释

#include<bits/stdc++.h>
using namespace std;
int a[60],up[60],down[60],res,n;
void dfs(int now,int cnt_u,int cnt_d) //cnt_u和cnt_d分别表示目前以后的上升子序列和下降子序列的数量。
{if(cnt_u + cnt_d >= res) return ; //如果答案不会更优,直接剪枝if(now == n + 1){res = min(res , cnt_u+cnt_d); //更新答案。return ;}int i=1;for(;i<=cnt_u;i++)if(up[i] < a[now])break;//如果按这个贪心思路接下去,up数组一定是单调的,直接遍历到第一个满足条件的即可。int info = up[i];up[i] = a[now]; //把a接在后面dfs(now + 1 , max(cnt_u , i) , cnt_d); //这里如果没有找到满足条件的up得到的i会是cnt_u+1,也就是加了一个新的序列。up[i] = info;for(i=1;i<=cnt_d;i++)if(down[i] > a[now])break;info = down[i];down[i] = a[now];dfs(now + 1 , cnt_u , max(cnt_d , i));down[i] = info;
}
int main()
{while(cin>>n&&n){for(int i=1;i<=n;i++) cin>>a[i];res = 100;dfs(1,0,0);cout << res << endl;}
}

Acwing 272-最长公共上升子序列

最长公共上升子序列

Think

直接类比公共子序列列出dp状态:dp[i][j]表示a[1 ~ i] 和b[1~j]中以b[j]结尾的最长公共上升子序列的长度,接下来考虑转移式。

状态转移:

        按照是否包含a[i]划分

1.如果不包含a[i],dp[i][j] = dp[i-1][j]

2.如果包含a[i],我们进行进一步划分,考虑序列的倒数第二个元素b[]是哪个值k

如果只有b[1],则dp[i][j] = 1

如果k = 1,则dp[i][j] = dp[i-1][1] + 1

如果k = 2,则dp[i][j] = dp[i-1][2] + 1

.....

如果k = j - 1,则dp[i][j] = dp[i-1][j-1] + 1

要模拟上述结果,需要三层for循环,过不了

优化:

        我们在枚举k时,可以发现我们实际上是在计算当a[i] > b[k] 时dp[i-1][k] 的前缀最大值,这样我们就可以把这层枚举取掉了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e3 + 10;
int a[maxn] , b[maxn] , dp[maxn][maxn];
int main()
{int n;cin>>n;for(int i = 1; i <= n;i ++)cin >> a[i];for(int i = 1; i <= n;i ++)cin >> b[i];for(int i = 1;i <= n;i ++){int MAX = 1;for(int j = 1;j <= n; j++){dp[i][j] = dp[i-1][j];if(a[i] == b[j]) dp[i][j] = max(dp[i][j] , MAX);if(a[i] > b[j]) MAX = max(MAX , dp[i-1][j] + 1);}}int res=0;for(int i=1;i<=n;i++)res = max(res , dp[n][i]);cout << res << endl;
}

最长上升子序列(LIS) 学习总结相关推荐

  1. 最长上升子序列(LIS) nlogn解法

    文章目录 经典DP解法O(n^2) dp+二分法(O(nlogn)) 最长上升子序列LIS:Longest increasing subsequence 题目链接:Leetcode300. 最长递增子 ...

  2. 最长上升子序列(LIS)长度

    转自:http://www.slyar.com/blog/poj-2533-cpp.html POJ 2533 Longest Ordered Subsequence 属于简单的经典的DP,求最长上升 ...

  3. 耐心排序之最长递增子序列(LIS)

    目录 一.问题引入 1.最长递增子序列(LIS) 2.问题分析 3.代码实现 4.问题思考 二.耐心排序 1.基本介绍 2.操作步骤 3.代码实现 三.俄罗斯套娃信封问题 1.题目描述 2.问题分析 ...

  4. 最长上升子序列(LIS),牛客刷题

    目录: 最长上升子序列(LIS) 1.模板(数据较小) 2.模板(数据较大) 牛客刷题 1. 牛客练习赛107A:如见青山 2.牛客小白月赛65A牛牛去购物 3.牛客小白月赛65B牛牛去购物 4.牛客 ...

  5. 最长上升子序列(LIS)长度及其数量

    例题51Nod-1376,一个经典问题,给出一个序列问该序列的LIS以及LIS的数量. 这里我学习了两种解法,思路和代码都是参考这两位大佬的: https://www.cnblogs.com/reve ...

  6. 最长上升子序列(LIS)的求法

    最长上升子序列(LIS) 给定一个长度为N的序列A 满足: 1. 1<=x1< x2< x3<-xk<=N 2. A[x1] < A[x2] < A[x3] ...

  7. 最长上升子序列 (LIS算法(nlong(n)))

    设 A[t]表示序列中的第t个数,F[t]表示从1到t这一段中以t结尾的最长上升子序列的长度,初始时设F [t] = 0(t = 1, 2, ..., len(A)).则有动态规划方程:F[t] = ...

  8. 最长上升子序列_动态规划 最长上升子序列LIS

    问题描述 最长上升子序列(LIS): 给定长度为n的序列,从中选中一个子序列,这个子序列需要单调递增,请问最长子序列(LIS)的长度? eg:1,5,2,3,11,7,9 则LIS序列为:1,2,3, ...

  9. 最长上升子序列LIS 动态规划 二分查找算法

    所谓LIS表示最长上升子序列,是面试的时候非常容易考察的问题.对于一个序列h1,h2,...hN,其中的子序列hi1,hi2,...hik,满足hi1<hi2<...<hik,那么这 ...

  10. 求最长上升子序列——LIS的O(nlogn)算法(二分)

    LIS的O(nlogn)算法(二分) 传送门:点我 O(n^2)解法:(n为4w,TLE) memset(dp,1,sizeof(dp)); int ans=-1; for(i=2; i<=n; ...

最新文章

  1. git 下载 github 上的代码
  2. python中矩阵与向量的区别
  3. [分享]毕业了【其实不想毕业】
  4. Java对MySQL数据库进行连接、查询和修改【转载】
  5. ZK 6中的MVVM初探
  6. 高等数学--集合概念
  7. 计算机软件编程英语词汇(二)
  8. Windows使用MinGW编译ffmpeg
  9. 神技 破解EXCEL工作表保护密码
  10. MIPI扫盲系列博文
  11. 无线网可拼服务器吗6,当WiFi 6遇到了WiFi 6+,我们的网速真的变快了吗?
  12. daytime协议的服务器和客户端程序,用socket套接字实现daytime协议服务器和客户端程序.doc...
  13. Android运营商名称显示之PLMN的读取(原)
  14. Linux安装搜狗输入法-openSUSE
  15. windows10如何修改hosts文件,微软官方回复,博主亲测有效
  16. 安卓交流社区!阿里P8架构师的Android大厂面试题总结,详细的Android学习指南
  17. python开发项目管理平台_基于Python的软件项目管理系统.doc
  18. 大话数字化转型-第三季:量变引起质变
  19. UE4开发常见问题:导入插件、代码调试、格式异常
  20. WWDC 2018 | 软件全面迭代更新,无硬件发布

热门文章

  1. Mybatis数据持久化
  2. iOS10.3.3 iPhone5使用爱思助手越狱后安装完openssl,afc2失效的解决办法
  3. rabbitmq默认guest无法登录的问题解决
  4. error: ‘ULONG_MAX’ was not declared in this scope
  5. HIT 软件构造 lab2
  6. 自学Python第二十二天- Django框架(一)创建项目、APP、快速上手、请求和响应流程、模板、数据库操作
  7. 对PHM铣刀磨损数据进行分析
  8. git如何新建分支进行开发
  9. ERLANG日期与时间
  10. [UE4笔记] 根据日期判断该天是周几