• 【动态规划】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

  • 相关阅读:
    二维数组实现八皇后问题
    解决Java接口内部类的main()方法无法打印输出的问题
    hbase shell 常见命令
    hbase-0.94 Java API
    JavaMail简单版实验测试
    经典KMP算法C++与Java实现代码
    Hadoop之倒排索引
    哈希哈希
    Servlet和JSP学习指导与实践(三):JSP助阵
    Servlet和JSP学习指导与实践(二):Session追踪
  • 原文地址:https://www.cnblogs.com/OvOq/p/14853207.html
Copyright © 2020-2023  润新知