最长上升子序列
题目链接:
- https://www.acwing.com/problem/content/897/ (元素小于 1000 1000 1000个)
2. https://www.acwing.com/problem/content/898/ (元素小于 100000 100000 100000个)
解法:
- 动态规划 O ( n 2 ) O(n^2) O(n2)复杂度过高
- 贪心加二分 O ( n l o g n ) O(nlogn) O(nlogn)推荐
- d p dp dp加线段树 O ( n l o g n ) O(nlogn) O(nlogn)太难
算法一:动态规划
状态表示: f i fi fi
- f i fi fi 表示从第一个数开始算,以第 i i i 个数结尾的最长上升子序列
状态计算:
- 如果我们选第 1 1 1 个数,且第 1 1 1 个数小于第 i i i 个数,那么对应的就是 f 1 f_1 f1
- 如果我们选第 2 2 2 个数,且第 2 2 2 个数小于第 i i i 个数,那么对应的就是 f 2 f_2 f2
- 如果我们选第 j j j 个数,且第 j j j 个数小于第 i i i 个数,那么对应的就是 f j f_j fj
- 所以状态转移方程就是 f i = max 1 ≤ j < i & a j < a i { f j + 1 } f_{i}=\max {1 \leq j<i \& a{j}<a_{i}}\left\{f_{j}+1\right\} fi=max1≤j<i&aj<ai{fj+1}
初始化:
- 因为每一个数以自己结尾的最长上升子序列的长度为 1 1 1 ,所以初始 f i 1 ≤ i ≤ n = 1 \underset{1 \leq i \leq n}{f_{i}}=1 1≤i≤nfi=1
答案:
- 根据定义,由于每一个数都有可能成为最长上升子序列的最后一个数,所以答案就是 m a x 1 ≤ i ≤ n f i \underset{1 \leq i \leq n}{max}{f_i} 1≤i≤nmaxfi
cpp
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
//f[i]表示以第i个元素结尾的最大长度
const int N=1e3+10;
int n;
int q[N],f[N];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
cin>>q[i];
for (int i=1;i<=n;i++)
{
f[i]=1;
for (int j=1;j<i;j++)
{
if(q[i]>q[j])
f[i]=max(f[i],f[j]+1);
}
}
cout<<*max_element(f+1,f+1+n)<<endl;
return 0;
}
算法二:贪心+二分
- 状态表示:
f[i]
表示长度为 i i i的最长上升子序列,末尾最小的数字 。(长度为 i i i的最长上升子序列所有结尾中,结尾**最小 m i n min min**的)即长度为 i i i的子序列末尾最小元素是什么。 - 状态计算:对于每一个
w[i]
, 如果大于f[cnt-1]
(下标从 0 0 0开始, c n t cnt cnt长度的最长上升子序列,末尾最小的数字),那就 c n t + 1 cnt+1 cnt+1,使得最长上升序列长度 + 1 +1 +1,当前末尾最小元素为w[i]
。 若w[i]
小于等于f[cnt-1]
,说明不会更新当前的长度,但之前末尾的最小元素要发生变化,找到第一个 大于或等于 (这里不能是大于)w[i]
,更新以那时候末尾的最小元素。 f[i]
一定以一个单调递增的数组,所以可以用二分法来找第一个大于或等于w[i]的数字。
cpp
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
const int N=1e3+10;
int n;
int w[N],f[N];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
cin>>w[i];
int len=0;//f数组的长度
f[++len]=w[1];
for (int i=2;i<=n;i++)
{
if(w[i]>f[len])f[++len]=w[i];
//比它小,二份一个位置给它放
else
f[lower_bound(f+1,f+1+len,w[i])-f]=w[i];//lower_bound返回一个大于等于该数指针,该指针减首地址就是下标
}
cout<<len<<endl;//得到的序列不一定合法
return 0;
}
cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int LIS(const vector<int> &a){
vector<int> result;
for (int n:a) {
auto it= lower_bound(result.begin(),result.end(),n);
if (it==result.end()) result.push_back(n);
else *it=n;
}
return result.size();
}
int LIS_dp(vector<int> &a){
int n=a.size();
if (n==0) return 0;
vector<int> dp(n,1);
int maxLen=1;
for (int i = 1; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (a[i]>a[j]) {
dp[i]=max(dp[i],dp[j]+1);
}
}
maxLen=max(maxLen,dp[i]);
}
return maxLen;
}
int main(){
int n;
cin>>n;
vector<int> a(n);
for (int &it:a) cin>>it;
cout<<LIS(a)<<endl;
return 0;
}