洛谷P1020 导弹拦截——二分优化LIS问题

对于求最长上升子序列问题,朴素算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),数据范围在 1 0 5 10^5 105以上时会超时。
P1020 [NOIP1999 普及组] 导弹拦截为例,对于第一问求最长不下降子序列及第二问求最长上升子序列,可以采用二分的方法将时间复杂度降为 O ( n l o g n ) O(nlogn) O(nlogn),具体操作如下

  • 对于dp数组,我们在 d p [ i ] dp[i] dp[i]中存放长度为 i i i的上升子序列的尾元素。例如 d p [ 3 ] dp[3] dp[3]中存放长度为 3 3 3的上升子序列的末尾元素。

  • 在扫描数列过程中记录当前最长上升子序列长度为 m a x l e n maxlen maxlen

  • d p [ 1 ] dp[1] dp[1]初始化为数列第一个元素( d p [ 1 ] = a [ 1 ] dp[1]=a[1] dp[1]=a[1]),而后对数列进行扫描:若当前元素大于当前最长上升子序列的尾元素,即
    a [ i ] > d p [ m a x l e n ] a[i]>dp[maxlen] a[i]>dp[maxlen],那么当前数字可以加入最长上升子序列,则 m a x l e n + + maxlen++ maxlen++,并将该长度的末尾值设置为 a [ i ] a[i] a[i]。反之,寻找该元素是否可以替换长度为1~maxlen的上升子序列的末尾值。因为对于相同长度的上升子序列,其尾元素越小,在向后扫描的过程中越可能找到加入该子序列的元素。 由于对于长度递增的序列,其尾元素值也必定递增,所以对于这一过程,我们可以采用二分来实现。

具体实现代码如下

#include<bits/stdc++.h>
#define ll long long

using namespace std;
const ll mod=1e6+7;
const int INF=0x7fffff;
int a[100005]; //原数组
int dp1[100005]; //存放非上升子序列长度为i的末尾元素
int dp2[100005]; //存放上升子序列长度为i的末尾元素
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n=0;
    while(scanf("%d",&a[++n])!=EOF){}
    n--;

    /*
        以下为第一问实现部分
    */
    dp1[1]=a[1]; //将长度为1的序列尾元素初始化为1
    int maxlen=1;  //初始长度为1
    for(int i=2;i<=n;i++)
    {
        //该元素大于当前最大长度序列的尾元素,则直接加入
        if(a[i]<=dp1[maxlen])
            dp1[++maxlen]=a[i];

        //该元素小于等于当前最大长度序列的尾元素,则查找是否能更新其余长度的尾元素
        else
        {
            int l=1,r=maxlen,mid;
            while(l<r)
            {
                mid=(l+r)/2;
                if(a[i]<=dp1[mid])
                    l=mid+1;
                else
                    r=mid;
            }
            dp1[l]=max(dp1[l],a[i]);
        }
    }
    cout<<maxlen<<endl;

    /*
        以下为第二问实现部分
    */
    fill(dp2+1,dp2+n+1,INF); //由于寻找LIS是将尾元素值尽可能变小,所以要将dp数组初始化为INF
    //以下实现过程同第一问
    dp2[1]=a[1];
    maxlen=1;
    for(int i=2;i<=n;i++)
    {
        int l=1,r=maxlen,mid;
        if(a[i]>dp2[maxlen])
        {
            dp2[++maxlen]=a[i];
        }
        else
        {
            while(l<r)
            {
                mid=(l+r)/2;
                if(a[i]<=dp2[mid])
                    r=mid;
                else
                    l=mid+1;
            }
            dp2[l]=min(dp2[l],a[i]);
        }
    }
    cout<<maxlen<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值