POJ1836Alignment双向LIS(最长上升子序列)(LIS+LSD)+dp

题意

给出一列士兵的身高(会有重复),要求求出剔除最少士兵使得每一个士兵往左或者右看可以看到该队列的尽头士兵,原有的位置不得改变。

注意

  1. 不能直接排序去剔除除去最高士兵以外有重复的元素,因为题目要求原来的队列顺序不变。

  2. A soldier see an extremity if there isn't any soldiers with a higher or equal height than his height between him and that extremity. 也就是说在最后得到的队列中,除了最高的那个士兵可以有重复身高之外,其他的士兵的身高不允许有重复。(这里需要纠正一下,最高的身高不允许有重复,不仅如此,所有的身高都不允许有重复,否则往左或者往右看都看不过去,会被等身高的人挡住)。即最多有一个极值。

思路

相当于给一列数num[n],要求求删掉最少的个数,任取其中一个元素num[i],满足:1.num[0]num[i]单调递增;2.num[i]num[n-1]单调递减。即最多有一个极值。

所以我们只需要:求出一个LIS一个LDS(需要倒着扫),然后从队首到队尾枚举位置,最后ans=n−max(dp1[i]+dp2[i])。

原数据:1.86 1.86 1.30621 2 1.4 1 1.97 2.2
最长上升子序列:1.86
        1.30621 2
        1.30621 1.4 (1.4去替换掉了2的位置)
        1 1.4 1.97 2.2 (1比1.30621更优,去替换掉了1.30621的位置,而后面的数据不需要更新)(最后凭借这个序列求出最长长度,这个的最长长度是和正确排序得出的长度是一样的)
       (1.30621 1.4 1.97 2.2 )(而这个长度与上面那一组数据长度一样,这个是正确排序的长度)
        最长长度:4
最长下降子序列:1.86 1.30621
        2 1.30621
        2 1.4
        2 1.4 1
        2 1.97 1
        2.2 1.97 1
        (1.86 1.30621 1;1.86 1.4 1;2 1.4 1)
        最长长度:3
(需要注意的是:求出来的只是序列的最长上升或下降长度,但是里面的元素不一定是按照上升或者下降的顺序进行排好的,只是用一个更优的数据去替换掉了原有的数据的位置。)

针对该题时间复杂度问题:对于最长上升子序列问题有两种写法,正常写O(n2),怕超时可以用O(nlogn)的写法。

AC代码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

float a[1010];
int dpup[1010],dpdown[1010];

int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin>>n;
    memset(dpup,0,sizeof(dpup));
    memset(dpdown,0,sizeof(dpdown));
    for(int i=0; i<n; i++)
        cin>>a[i];
    for(int i=0; i<n; i++)
    {
        dpup[i]=1;
        for(int j=0; j<i; j++)
        {
            if(a[j]<a[i])
            {
                dpup[i]=max(dpup[i],dpup[j]+1);
            }
        }
    }
//
//    for(int i=0; i<n; i++)
//    {
//        dpdown[i]=1;
//        for(int j=0; j<i; j++)
//        {
//            if(a[j]>a[i])
//            {
//                dpdown[i]=max(dpdown[i],dpdown[j]+1);
//            }
//        }
//
//这两个上升和下降序列单纯判断是没有问题的,但是在这一题中,这样判断会出现交叉的情况,所以不能这样子写

    for(int i=n-1; i>=0; i--) //这里需要倒着扫
    {
        dpdown[i]=1;
        for(int j=n-1; j>i; j--)
        {
            if(a[i]>a[j])
            {
                dpdown[i]=max(dpdown[j]+1,dpdown[i]);
            }
        }
    }
    int ans=-inf;
    for(int i=0; i<n; i++)
    {
        for(int j=i+1; j<n; j++)
        {
//            if(a[i]==a[j])
//                ans=max(ans,dpup[i]+dpdown[j]-1);
//            else
            ans=max(ans,dpup[i]+dpdown[j]);
        }
    }
    //这里的第二层循环从i+1开始,已经间接排除掉了顶峰会出现两个相等的情况
    //不存在顶峰出现三个甚至更多的情况,因为之前求得时候一个单调递增,一个单调递减,要是重复只会重复一次
    cout<<n-ans<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/OFSHK/p/11220951.html