浅谈最长上升子序列

求最长上升子序列长度

f[i]定义以a[i]结尾的最长上升子序列长度枚举每个数i,用j枚举从1~i-1,如果当前f[j]<f[i]说明可以更新。实际就是从前往后枚举到那个数之前,有状态就更新。O(n^2)

#include<bits/stdc++.h>
using namespace std;
int f[1005],a[1005];
int main(){
    int n,res=0;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i],f[i]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++)
            if(a[j]<a[i])f[i]=max(f[i],f[j]+1);
        res=max(res,f[i]);    
    }
    cout<<res<<'\n';
    return 0;
}

求最长上升子序列的字典序最小的方案

原题链接

来求字典序最小方案O(n^2)做法:上面求出了他的最长子序列,我们接下来打印路径,标记最大值和他的下标,从后枚举即可。一些细节以及证明下面给出。

证明:字典序最小的选取(反证法)

  1. 先确定maxlen的下标末尾,即 m a x f [ 1 − n ] max{f[1-n]} maxf[1n]考,虑i<j,f[i]=f[j]的情况此时的arr[i]一定是>=arr[j],因为若arr[i]<arr[j],那么f[j]=f[i]+k(k>0,与f[i]=f[j]矛盾),综上字典序最小应该是选arr[j]
  2. 找pos一定是在f[i]相等的最后一个,比如1,2,5,4。pos=4(下标从1开始)
//请勿直接粘贴运行,已省略头文件
int f[100005],a[100005];
int main(){
    int n,maxl=0,pos=0;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i],f[i]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++)
            if(a[j]<a[i])f[i]=max(f[i],f[j]+1);
        if(f[i]>=maxl){//=不能省
            maxl=f[i];
            pos=i;
        }
    }
    vector<int>res;
    for(int i=pos;i>=1;i--){
        if(maxl==f[i]){
            maxl--;
            res.push_back(a[i]);
        }
    }
    for(int i=res.size()-1;i>=0;i--)
        cout<<res[i]<<' ';
    return 0;
}

贪心求最长上升子序列长度

实际上就是去解决二层循环的问题,我们不妨用一个vector来维护这个最长上升子序列。枚举每个数,当前这个数比v末端大,说明加进去会增加序列长度,否则我们把这个数放到第一个大于或等于它的数位置上。可以保证后面的只会更优。

int a[100005];
int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    vector<int>st;
    st.push_back(a[0]);
    for(int i=1;i<n;i++){
        if(a[i]>st.back())
            st.push_back(a[i]);//否则替换第一个>或者=的位置上
        else *lower_bound(st.begin(),st.end(),a[i])=a[i];   
    }
    cout<<st.size()<<'\n';
    return 0;
}

说明:(l来源于别处,忘记了在哪,就没标出处~)最难理解的地方在于栈中序列虽然递增,但是每个元素在原串中对应的位置其实可能是乱的,那为什么这个栈还能用于计算最长子序列长度?实际上这个栈【不用于记录最终的最长子序列】,而是【以stk[i]结尾的子串长度最长为i】或者说【长度为i的递增子串中,末尾元素最小的是stk[i]】。理解了这个问题以后就知道为什么新进来的元素要不就在末尾增加,要不就替代第一个大于等于它元素的位置。这里的【替换】就蕴含了一个贪心的思想,对于同样长度的子串,我当然希望它的末端越小越好,这样以后我也有更多机会拓展。

贪心求最长上升子序列方案

O(n*logn)

传送门

可以在牛客讨论里面看大佬详解。

const int N=1e5+5;
int a[N];
vector<int>endd,f;//endd[i]=val长度为i的序列末尾元素是val,
//f[i]=len以a[i]结尾的最长子序列长度是len
int main() {
    int n;
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    endd.emplace_back(a[0]);//长度为1的序列末尾元素是a[0]
    f.emplace_back(1);//a[0]元素结尾的长度是1
    for(int i=1;i<n;i++){//从第二个元素开始遍历
        if(a[i]>endd.back()){//当前元素大的直接放到endd末尾
            endd.emplace_back(a[i]);
            f.emplace_back(endd.size());//更新a[i]元素末尾长度
        }else{//否则贪心的寻找第一个>=a[i]元素的位置,把a[i]替换掉endd对应的pos位置(贪心)
            int pos=lower_bound(endd.begin(),endd.end(),a[i])-endd.begin();
            endd[pos]=a[i];//此时替换
            //更新以a[i]结尾的数,最长递增序列长度是pos+1,因为pos是从0开始的
            f.emplace_back(pos+1);
        }
    }//此时endd数组size就是长度,但是我们需要把里面不符合的替换一下
    //用f数组寻找f从n-1~0当f[i]==max_len时
    //说明以a[i]结尾的元素他的最长子序列是max_len,然后递减即可
    for(int i=n-1,m_index=endd.size();m_index>0;i--){
        if(f[i]==m_index){
            endd[--m_index]=a[i];
        }
    }
    for(int i=0;i<endd.size();i++)cout<<endd[i]<<' ';
    return 0;
}//求字典序最大即lower_bound变成upper_bound
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值