求最长上升子序列长度
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)做法:上面求出了他的最长子序列,我们接下来打印路径,标记最大值和他的下标,从后枚举即可。一些细节以及证明下面给出。
证明:字典序最小的选取(反证法)
- 先确定maxlen的下标末尾,即 m a x f [ 1 − n ] max{f[1-n]} maxf[1−n]考,虑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]
- 找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