最长上升子序列(LIS) 学习总结
前言
鉴于本蒟蒻的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) 学习总结相关推荐
- 最长上升子序列(LIS) nlogn解法
文章目录 经典DP解法O(n^2) dp+二分法(O(nlogn)) 最长上升子序列LIS:Longest increasing subsequence 题目链接:Leetcode300. 最长递增子 ...
- 最长上升子序列(LIS)长度
转自:http://www.slyar.com/blog/poj-2533-cpp.html POJ 2533 Longest Ordered Subsequence 属于简单的经典的DP,求最长上升 ...
- 耐心排序之最长递增子序列(LIS)
目录 一.问题引入 1.最长递增子序列(LIS) 2.问题分析 3.代码实现 4.问题思考 二.耐心排序 1.基本介绍 2.操作步骤 3.代码实现 三.俄罗斯套娃信封问题 1.题目描述 2.问题分析 ...
- 最长上升子序列(LIS),牛客刷题
目录: 最长上升子序列(LIS) 1.模板(数据较小) 2.模板(数据较大) 牛客刷题 1. 牛客练习赛107A:如见青山 2.牛客小白月赛65A牛牛去购物 3.牛客小白月赛65B牛牛去购物 4.牛客 ...
- 最长上升子序列(LIS)长度及其数量
例题51Nod-1376,一个经典问题,给出一个序列问该序列的LIS以及LIS的数量. 这里我学习了两种解法,思路和代码都是参考这两位大佬的: https://www.cnblogs.com/reve ...
- 最长上升子序列(LIS)的求法
最长上升子序列(LIS) 给定一个长度为N的序列A 满足: 1. 1<=x1< x2< x3<-xk<=N 2. A[x1] < A[x2] < A[x3] ...
- 最长上升子序列 (LIS算法(nlong(n)))
设 A[t]表示序列中的第t个数,F[t]表示从1到t这一段中以t结尾的最长上升子序列的长度,初始时设F [t] = 0(t = 1, 2, ..., len(A)).则有动态规划方程:F[t] = ...
- 最长上升子序列_动态规划 最长上升子序列LIS
问题描述 最长上升子序列(LIS): 给定长度为n的序列,从中选中一个子序列,这个子序列需要单调递增,请问最长子序列(LIS)的长度? eg:1,5,2,3,11,7,9 则LIS序列为:1,2,3, ...
- 最长上升子序列LIS 动态规划 二分查找算法
所谓LIS表示最长上升子序列,是面试的时候非常容易考察的问题.对于一个序列h1,h2,...hN,其中的子序列hi1,hi2,...hik,满足hi1<hi2<...<hik,那么这 ...
- 求最长上升子序列——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; ...
最新文章
- git 下载 github 上的代码
- python中矩阵与向量的区别
- [分享]毕业了【其实不想毕业】
- Java对MySQL数据库进行连接、查询和修改【转载】
- ZK 6中的MVVM初探
- 高等数学--集合概念
- 计算机软件编程英语词汇(二)
- Windows使用MinGW编译ffmpeg
- 神技 破解EXCEL工作表保护密码
- MIPI扫盲系列博文
- 无线网可拼服务器吗6,当WiFi 6遇到了WiFi 6+,我们的网速真的变快了吗?
- daytime协议的服务器和客户端程序,用socket套接字实现daytime协议服务器和客户端程序.doc...
- Android运营商名称显示之PLMN的读取(原)
- Linux安装搜狗输入法-openSUSE
- windows10如何修改hosts文件,微软官方回复,博主亲测有效
- 安卓交流社区!阿里P8架构师的Android大厂面试题总结,详细的Android学习指南
- python开发项目管理平台_基于Python的软件项目管理系统.doc
- 大话数字化转型-第三季:量变引起质变
- UE4开发常见问题:导入插件、代码调试、格式异常
- WWDC 2018 | 软件全面迭代更新,无硬件发布
热门文章
- Mybatis数据持久化
- iOS10.3.3 iPhone5使用爱思助手越狱后安装完openssl,afc2失效的解决办法
- rabbitmq默认guest无法登录的问题解决
- error: ‘ULONG_MAX’ was not declared in this scope
- HIT 软件构造 lab2
- 自学Python第二十二天- Django框架(一)创建项目、APP、快速上手、请求和响应流程、模板、数据库操作
- 对PHM铣刀磨损数据进行分析
- git如何新建分支进行开发
- ERLANG日期与时间
- [UE4笔记] 根据日期判断该天是周几