动态规划o( N 2 N^2 N2)的解法
- 我们先确定状态:
dp[k]:表示以 a k a_k ak为终点的最长上升子序列的长度。可以看到,该状态满足无后效性 - 然后确定递推式
初始状态:maxLen(1)=1
maxLen(k)=max {maxLen(i):1<=i<k, a i < a k a_i<a_k ai<ak,且k!=1}+1
如果找不到这样的i,那么有maxLen(k)=1 - 代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1010;
int a[MAXN];
int maxLen[MAXN],N;
int main(){
int i,j,k;
cin>>N;
for(i=1;i<=N;i++){
cin>>a[i];
maxLen[i]=1;
}
for(i=2;i<=N;i++){
for(j=1;j<i;j++){
if(a[i]>a[j]){
maxLen[i]=max(maxLen[i],maxLen[j]+1);
}
}
}
//利用C++的max_element函数求数组的最大值
cout<<*max_element(maxLen+1,maxLen+N+1)<<endl;
}
贪心+二分o( N l o g N Nlog_N NlogN)的解法
- 这种解法是个一种叫做纸牌接龙的游戏相关,感兴趣的可以自己搜索一下,我只说下算法操作的步骤。
- 维护一个递增的数组B,如果A[i]大于数组B中的所有元素,则将其放到数组B的后面
- 否则我们用lower_bound()函数找到第一个大于等于A[i]的数,并用A[i]将此数替换掉
- 这个算法用到了贪心的思想,使数组B中的数尽可能的小,以便于后面的数更容易的进来。要注意的是,数组B中的数并不是LIS序列,只是数组的长度等于LIS的长度。至于怎么求LIS序列,后面我将讲到。
- 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define scf scanf
#define prf printf
const ll MAX_N=1e5+7;
ll T,N;
ll A[MAX_N],B[MAX_N];
ll do_LIS(){
ll i,j,k,cnt=0;
for(i=0;i<N;i++){
ll L=0,R=cnt-1,mid,res=-1;
while(L<=R){
mid=L+(R-L)/2;
if(B[mid]>=A[i]){
res=mid;
R=mid-1;
}
else{
L=mid+1;
}
}
if(res==-1){
//B数组中的所有数都小于A[i]
B[cnt]=A[i];
cnt++;
}
else{
B[res]=A[i];
}
}
return cnt;
}
int main()
{
ll i,j,k=0;
scf("%lld",&N);
for(i=0;i<N;i++){
scf("%lld",&A[i]);
}
ll res=do_LIS();
prf("%lld\n",res);
return 0;
}
- C++的lower_bound()函数版本
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define scf scanf
#define prf printf
const ll MAX_N=1e5+7;
ll T,N;
ll A[MAX_N],B[MAX_N];
ll do_LIS(){
ll i,j,k,cnt=0;
for(i=0;i<N;i++){
ll pos=lower_bound(B,B+cnt,A[i])-B;
if(pos==cnt){
cnt++;
}
B[pos]=A[i];
}
return cnt;
}
int main()
{
ll i,j,k=0;
scf("%lld",&N);
for(i=0;i<N;i++){
scf("%lld",&A[i]);
}
ll res=do_LIS();
prf("%lld\n",res);
return 0;
}
- 前面说了, 数组B存放的并不是LIS的序列,此时需要一个数组index[],记录数组A[i]在数组B中位置,然后倒序查找输出
- 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define scf scanf
#define prf printf
const ll INF=0x3f3f3f3f;
const ll MAX_N=1e5+7;
ll T,N;
ll A[MAX_N],B[MAX_N],index[MAX_N];
vector<ll>V;
ll do_LIS(){
ll i,j,k,cnt=0;
for(i=0;i<N;i++){
ll pos=lower_bound(B,B+cnt,A[i])-B;
if(pos==cnt){
cnt++;
}
B[pos]=A[i];
index[i]=pos;
}
return cnt;
}
int main()
{
ll i,j,k=0;
scf("%lld",&N);
for(i=0;i<N;i++){
scf("%lld",&A[i]);
}
ll res=do_LIS();
prf("%lld\n",res);
ll maxx=INF,cnt=res-1;
for(i=N-1;i>=0;i--){
//反向存的
if(cnt<0)
break;
if(index[i]==cnt&&A[i]<maxx){
V.push_back(A[i]);
cnt--;
maxx=A[i];
}
}
reverse(V.begin(),V.end());
for(i=0;i<V.size();i++){
prf("%lld ",V[i]);
}
return 0;
}
最长下降子序列
其实就是将数组反过来求出最长上升子序列的长度。
最长不下降子序列
只需要将求LIS的语句稍加修改就可以了
- 动态规划版本
将A[i]>A[j]----->A[i]>=A[j] - 代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1010;
int a[MAXN];
int maxLen[MAXN],N;
int main(){
int i,j,k;
cin>>N;
for(i=1;i<=N;i++){
cin>>a[i];
maxLen[i]=1;
}
for(i=2;i<=N;i++){
for(j=1;j<i;j++){
if(a[i]>=a[j]){
maxLen[i]=max(maxLen[i],maxLen[j]+1);
}
}
}
//利用C++的max_element函数求数组的最大值
cout<<*max_element(maxLen+1,maxLen+N+1)<<endl;
}
- 贪心+二分版本
将lower_bound()函数改为upper_bound()函数,在数组B中遇到等于A[i]的情况,我们不将其覆盖。 - 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define scf scanf
#define prf printf
const ll MAX_N=1e5+7;
ll T,N;
ll A[MAX_N],B[MAX_N];
ll do_LIS(){
ll i,j,k,cnt=0;
for(i=0;i<N;i++){
ll pos=upper_bound(B,B+cnt,A[i])-B;
if(pos==cnt){
cnt++;
}
B[pos]=A[i];
}
return cnt;
}
int main()
{
ll i,j,k=0;
scf("%lld",&N);
for(i=0;i<N;i++){
scf("%lld",&A[i]);
}
ll res=do_LIS();
prf("%lld\n",res);
return 0;
}