最长上升子序列(及打印路径) —— 导弹拦截题解 (LIS / Dilworth定理 / dp / 贪心+二分)

最长上升子序列( LIS )问题:即求一个序列中最长的上升子序列的长度。

应用: LIS经常用于确定一个代价最小的调整方案,使一个序列变为升序。只需要固定LIS中的元素,调整其他元素即可。


在这里插入图片描述

题解

第一问很明显可以看出是LIS的类似问题,只不过是变成求最长不上升子序列长度。

第二问根据Dilworth定理,我们将其反过来:“一个偏序集的最少链划分数等于其最长反链的长度” 同样成立,所以我们可以得出求最少需要多少套导弹拦截系统其实就是求最少链划分数,也就相当于求最长反链的长度,所以第二问就转化为一个求最长上升子序列长度的问题了。
Dilworth定理:一个偏序集的最少反链划分数等于其最长链的长度.

关于偏序集与dilworth定理

做法1:dp ( O(n^2) )

LIS的dp做法( O(n^2) ):
(1)定义:dp[ i ]表示以a[ i ]为末尾的最长上升子序列的长度。
(2)转移方程:dp[ i ] = max{ dp[ i ], dp[ j+1 ] | j < i 且 a[ j ] < a[ i ] }
(3)初始条件:dp[ i ] = 1

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int a[100002], n = 0, dp[100002]={}, ret1=0, ret2=0;
    while (~scanf("%d", &a[++n])); n--;//由于某种原因这里需-1后n才是输入的个数
    /*求最长不上升子序列长度*/
    for (int i = 1; i <= n; i++){
        dp[i]=1;
        for (int j = 1; j < i; j++)
            if (a[i] <= a[j]) dp[i]=max(dp[i], dp[j]+1);
        ret1 = max(ret1, dp[i]);
    }
    printf("%d\n",ret1);
    /*求最长上升子序列长度*/
    for (int i = 1; i <= n; i++){
        dp[i]=1;
        for (int j = 1; j < i; j++)
            if (a[i] > a[j]) dp[i]=max(dp[i], dp[j]+1);
        ret2 = max(ret2, dp[i]);
    }
    printf("%d\n", ret2);
    return 0;
}

做法2:贪心思想+二分优化( O(nlogn) )

LIS的贪心+二分做法( O(nlogn) ):
从LIS的性质可知要想得到一个更长的上升子序列,该序列最后一个数必须尽量的小,才能越有利于后面接其他的数,变得更长。
比如1 4 9 3 5 7,当1 4 9遇到3时序列不能再变长了,这时我们可以将3和第一个大于3的数(即4)替换,变为1 3 9,下面再遇到5时同理,替换5-9,变为1 3 5,这时再遇到7便能继续变长为1 3 5 7。最后得到的1 3 5 7的个数就是1 4 9 3 5 7的最长的上升子序列长度,所以该序列的最长上升子序列长度为4。

通过上述例子,我们可以发现在替换时要进行替换的数准备插入到的序列是个有序序列,所以这里可以用二分去寻找插入位置进行优化,使得时间复杂度降低为nlogn。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+2;
int main()
{
    int a[maxn], n = 0, s1[maxn]={}, s2[maxn]={}, len1 = 1, len2 = 1;
    while (~scanf("%d", &a[++n])); n--;
    s1[1] = a[1], s2[1] = a[1];
    for(int i = 2; i <= n; i++){
    	/*求最长不上升子序列*/
        if (a[i] <= s1[len1]) s1[++len1] = a[i];
        else *upper_bound(s1+1, s1+len1+1, a[i], greater<int>()) = a[i]; /*因为是不上升序列,是小于等于的,所以用upper_bound*/
        /*求最长上升子序列*/
        if (a[i] > s2[len2]) s2[++len2] = a[i];
        else *lower_bound(s2+1, s2+len2+1, a[i]) = a[i]; /*因为是上升序列,是大于的,所以用lower_bound*/
    }
    printf("%d\n%d\n", len1, len2);
    return 0;
}

最长上升子序列dp做法打印路径:
/*递归法打印路径*/
#include<iostream>
#include<cstring>
using namespace std;
int vis[3100];
int n,a[3100],dp[3100],ret;
void dfs(int t)
{
    if(t==-1)return;
    dfs(vis[t]);
    cout<<a[t]<<' ';
}
int main()
{
    memset(vis,-1,sizeof(vis));
    int t;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i],dp[i]=1;
    for(int i=0;i<n;i++){
        for(int j=0;j<i;j++){
            if(a[j]<a[i]){
                if(dp[i]<dp[j]+1)dp[i]=dp[j]+1,vis[i]=j;
                ///dp[i]=max(dp[i],dp[j]+1);
            }
        }
        ret=max(ret,dp[i]);
        if(ret==dp[i])
           t=i;
    }
    cout<<ret<<endl;
    /*递归打印路径*/
    dfs(t);
    cout<<endl;
    return 0;
}
/*
9
3 5 2 8 6 4 7 9 11
*/
/*常规方法打印路径:*/
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int n,a[3100],dp[3100],ret;
int main()
{
    vector<int>ans; ///存储路径
    int t;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i],dp[i]=1; ///dp数组初始化为1
        
    for(int i=0;i<n;i++){
        for(int j=0;j<i;j++){
            if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1);
        }
        ret=max(ret,dp[i]);
        if(ret==dp[i])t=i; ///求出最长的上升子序列的最后一个数的下标
    }
    cout<<ret<<endl; ///输出长上升子序列的长度
    
    /*打印路径*/
    ans.push_back(a[t]);
    int k=a[t],j=t;
    for(int i=t-1;i>=0;i--){
        if(a[i]<=k&&dp[i]==dp[j]-1){
            ans.push_back(a[i]);
            k=a[i];
            j=i;
        }
    }
    for(int i=ans.size()-1;i>=0;i--)
        cout<<ans[i]<<' ';
    cout<<endl;
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值