• 【动态规划】LIS最长上升子序列【入门】


    一.最简单的最长上升子序列

    AcWing 895. 最长上升子序列
    这是一道典型的dp例题, dp的两个重要元素:状态表示和状态计算。其中维度的选择是很关键的,要求既能够表示出转移过程中的状态,而且能够计算出结果,在此基础上,要求维度尽可能小。
    我们这里可以用dp[i]来表示以第i个数结尾的数值上升的子序列的集合,属性是max;那么对于状态转移的话,我们可以这样考虑:每一个上升子序列以倒数第二个数选什么来分类。可以有以下情况,dp[i-1]=0(即当前是第一个数),a[k] (k=0,1,……i-1); 要注意这里面并不是每一个状态都存在,我们只需要把存在的状态进行转移就可。
    状态转移方程还是很好推的,我们记这个状态可以由上一个状态 j 转移过来,那么本状态的max就等于 j 状态的max+1(本次状态的长度) 。如下:

    dp[i]=dp[j]+1;
    

    结合上面两部分,整个转移方程也很容易写出来啦

    for(int j=1;j<i;j++)
    	if(a[j]<a[i]) //保证是上升子序列
            dp[i]=max(dp[i],dp[j]+1);
    

    最后,一定要记得给dp赋初值呀!
    完整代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1e6+7;
    ll dp[maxn],n,a[maxn];
    int main(){
        scanf("%lld",&n);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        for(int i=1;i<=n;i++){
            dp[i]=1;
            for(int j=1;j<i;j++)
                if(a[j]<a[i])
                    dp[i]=max(dp[i],dp[j]+1);
        }
        ll res=-1;
        for(int i=1;i<=n;i++) res=max(res,dp[i]);
        cout<<res<<endl;
        return 0;
    }
    

    二: 单调队列优化的最长上升子序列

    AcWing 896. 最长上升子序列 II
    朴素版本的我们已经写完了,接下来就是考虑优化的问题了。感觉优化时有点贪心的思想。
    举个栗子~
    就拿本题的样例吧~

      3  1  2  1  8  6  6
    

    根据我们在“一”中的思想,dp[i]表示 (balabala)(忘记了的回去看一下~)
    其实很容易能够知道,开始的时候,长度为1的上升子序列有两个:{3} , {1} 如果一个数能接到3的后面,也就能接到1的后面~对,就是这样!那么既然这样的话,第一个子序列就没有存在的意义啦(好残忍)。 因为接到1的后面更优。
    简单总结就是,对于每一组相同长度的上升子序列,我们可以只记录a[i]最小的那一组,保证最优解和高效性。

    同样的,我们存储一下不同长度下最小的结尾值,我们会发现结尾值和长度是正相关的。基于此性质,可以用单调队列来优化此dp。

    假如数组f现在存了数,当到了数组a的第i个位置时,首先判断a[i] > f[cnt] ? 若是大于则直接将这个数添加到数组f中,即f[++cnt] = a[i];这个操作时显然的。
    当a[i] <= f[cnt] 的时,我们就用a[i]去替代数组f中的第一个大于等于a[i]的数,因为在整个过程中我们维护的数组f 是一个递增的数组,所以我们可以用二分查找在 logn 的时间复杂的的情况下直接找到对应的位置,然后替换,即f[l] = a[i]。

    好了代码如下

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+7;
    int q[maxn],a[maxn],n,cnt;
    inline int read(){
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline int find(int x){
        int l=1,r=cnt;
        while(l<r){
            int mid=l+r>>1;
            if(q[mid]>=x) r=mid;
            else l=mid+1;
        }
        return l;
    }
    int main(){
        n=read();
        for(int i=1;i<=n;i++) a[i]=read();
        q[++cnt]=a[1];
        for(int i=2;i<=n;i++){
            if(a[i]>q[cnt]) q[++cnt]=a[i];
            else{
                int temp=find(a[i]);
                q[temp]=a[i];
            }
        }
        printf("%d
    ",cnt);
        return 0;
    }
    

    三.题目

    欢迎来玩~
    [传送门]

    1017. 怪盗基德的滑翔翼 - AcWing题库
    思路: 当确定完起点后,就转化成了在两个方向上分别求解LIS问题;
    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=110;
    int a[maxn],dp[maxn];
    int main(){
        int t;cin>>t;
        while(t--){
            int n;cin>>n;
            for(int i=1;i<=n;i++) cin>>a[i];
            memset(dp,0,sizeof dp);
            int res=-1;
            for(int i=1;i<=n;i++){
                dp[i]=1;
                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]);
            }
            for(int i=n;i>=1;i--){
                dp[i]=1;
                for(int j=n;j>i;j--)
                    if(a[j]<a[i])
                        dp[i]=max(dp[i],dp[j]+1);
                res=max(res,dp[i]);
            }
             cout<<res<<endl;   
        }
        return 0;
    }
    

    1014. 登山 - AcWing题库
    思路:
    本题有三个要求:1.每次所浏览景点的编号都要大于前一个浏览景点的编号。
    2.不连续浏览海拔相同的两个景点
    3.一旦开始下山,就不再向上走了
    分析这三个要求:可以得出最后得到的子序列是先上升再下降的,那么我们可以根据峰值来进行计算。记左边的以a[k]结尾的最长上升子序列长度 max = l [ k ] , 右边的以a[k] 开始的最长下降子序列长度 max = r [ k ] , 那么可得到

    dp[k]=l[k]+r[k]-1; //要除去本身
    

    代码:
    一定要记得初始化!

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1100;
    int a[maxn],l[maxn],r[maxn];
    int main(){
        int n;cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<=n;i++){
            l[i]=1;
            for(int j=1;j<i;j++)
                if(a[j]<a[i])
                    l[i]=max(l[i],l[j]+1);
        }
            
        for(int i=n;i>=1;i--){
            r[i]=1;
            for(int j=n;j>i;j--)
                if(a[j]<a[i])
                    r[i]=max(r[i],r[j]+1);
        }
            
        int res=-1;
        for(int i=1;i<=n;i++) res=max(res,l[i]+r[i]-1);
        cout<<res<<endl;
        return 0;
    }
    
    

    482. 合唱队形 - AcWing题库
    思路: 跟上题思路相同,只需最后输出总数减去最大值即可
    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1100;
    int a[maxn],l[maxn],r[maxn];
    int main(){
        int n;cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<=n;i++){
            l[i]=1;
            for(int j=1;j<i;j++)
                if(a[j]<a[i])
                    l[i]=max(l[i],l[j]+1);
        }
            
        for(int i=n;i>=1;i--){
            r[i]=1;
            for(int j=n;j>i;j--)
                if(a[j]<a[i])
                    r[i]=max(r[i],r[j]+1);
        }
            
        int res=-1;
        for(int i=1;i<=n;i++) res=max(res,l[i]+r[i]-1);
        cout<<n-res<<endl;
        return 0;
    }
    
    

    1012. 友好城市 - AcWing题库
    思路: 我们把北岸排好序之后会有a[i1].n<a[i2].n,(i1<i2)a[i1].n<a[i2].n,(i1<i2);假如对于i1,i2i1,i2,它们的南岸坐标 a[i1].s<a[i2].sa[i1].s<a[i2].s ,那么就会有交叉,而所有城市的友好城市不相同,所以我们应该建造一个严格上升的子序列。(摘自大佬博客)
    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=5100;
    struct node{
        int x,y;
    }a[maxn];
    int dp[maxn],n;
    bool cmp(node a,node b){
        return a.x<b.x;
    }
    int main(){
        cin>>n;
        for(int i=0;i<n;i++) cin>>a[i].x>>a[i].y;
        sort(a,a+n,cmp);
        int res=-1;
        for(int i=0;i<n;i++){
            dp[i]=1;
            for(int j=0;j<i;j++)
                if(a[i].y>a[j].y)
                    dp[i]=max(dp[i],dp[j]+1);
            res=max(res,dp[i]);
        }
        cout<<res<<endl;
        return 0;
    }
    

    1016. 最大上升子序列和 - AcWing题库
    思路: 跟LIS类似的,只不过初始化要变为a[i],转移方程为

    dp[i]=max(dp[i],dp[j]+a[i]);
    

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1100;
    int a[maxn],dp[maxn];
    int main(){
        int n;cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        int res=-1;
        for(int i=1;i<=n;i++){
            dp[i]=a[i];
            for(int j=1;j<i;j++)
                if(a[j]<a[i])
                    dp[i]=max(dp[i],dp[j]+a[i]);
            res=max(res,dp[i]);
        }
        cout<<res<<endl;
        return 0;
    }
    
    

    待补:
    1010. 拦截导弹 - AcWing题库

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+7;
    int n,h[maxn],dp[maxn],q[maxn];
    int main(){
        int x;
        while(cin>>h[n]) n++;
        int res=0,cnt=0;
        for(int i=0;i<n;i++){
            dp[i]=1;
            for(int j=0;j<i;j++)
                if(h[i]<=h[j])
                    dp[i]=max(dp[i],dp[j]+1);
            res=max(res,dp[i]);
        }
        cout<<res<<" ";
    /**贪心:从前往后扫描每个数,对于每个数
    情况一:如果现有的子序列的结尾都小于当前数 则创建新的子序列
    情况二:将当前数放到结尾大于等于他的最小子序列后
    **/
        for(int i=0;i<n;i++){
            int k=0;
            while(k<cnt&&q[k]<h[i]) k++;
            q[k]=h[i];
            if(k>=cnt) cnt++;
        }
        cout<<cnt<<endl;
        return 0;
    }
    

    187. 导弹防御系统 - AcWing题库
    272. 最长公共上升子序列 - AcWing题库

    #include<bits/stdc++.h>
    #define Cutele main
    using namespace std;
    inline int read(){
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    const int maxn=1100;
    char a[maxn],b[maxn];
    int dp[maxn][maxn],n,m;
    int Cutele(){
        scanf("%d%d",&n,&m);
        scanf("%s%s",a+1,b+1);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                if(a[i]==b[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
            }
        printf("%d
    ",dp[n][m]);
        return 0;
    }
    
    
    
    

    参考博客:AcWing 896. 最长上升子序列 II - AcWing
    AcWing 1012. 友好城市 - AcWing

  • 相关阅读:
    codeforces 616B Dinner with Emma
    codeforces 616A Comparing Two Long Integers
    codeforces 615C Running Track
    codeforces 612C Replace To Make Regular Bracket Sequence
    codeforces 612B HDD is Outdated Technology
    重写父类中的成员属性
    子类继承父类
    访问修饰符
    方法的参数
    实例化类
  • 原文地址:https://www.cnblogs.com/OvOq/p/14853207.html
Copyright © 2020-2023  润新知