LIS-最长上升子序列

最长上升子序列(Longest Increasing Subsequence) 也称LIS

我们首先要明确子序列和子串的区别,子序列可以不连续,子串必须连续

  • 举一个例子:1 5 7 0 4 5 6 1 2
    我们来找一下它的最长上升子序列,1,5,7这是一个,而且没有比7大的,说明以7结尾的子序列最长就是3;
    那么7不能是最后面的,我们又看到了6,所以1,5,6这又是一个,长度为3
    这样6也用过了,那么5也不能再用,所以我们看到1,4,5,6这个子序列长度是最长的为4
  • 在上面的例子中可以看出,这个子序列的找法应该是从头开始找,碰到一个比当前最长上升子序列末尾元素大的就把它加进来,长度加一,那么如果碰到一个小的怎么办呢?看上面的例子,显然是用它替换掉找好的最长上升子序列中第一个比它大的元素,就像例子中的用6换7,用4换5一样,最后每次都去取序列长度的最大值,这样就得到了最长上升子序列的长度,
  • 明白了原理就要尝试用程序实现,首先要开两个数组记录,一个记录原序列,另一个记录当前找到的子序列,然后用一个变量len存储当前长度
  • 最后这中间有一个查找的过程,在数组中找第一个比x大的元素,很明显可以用upper_bound函数,或者自己写一个二分查找
int solve(int n){
    
    
    f[1] = Data[0];
    int len = 1;
    for(int i=1;i<n;i++){
    
    
        if(Data[i]>f[len]) f[++len] = Data[i];
        else{
    
    
            int pos = upper_bound(f+1,f+len+1,Data[i]) - f;
            f[pos] = Data[i];
        }
    }
    return len;
}

题目链接:
板子题可以拿来过一下,它上面写错了不是LCS而是LIS

  • 接下来我们继续做题
    HDU5256
  • 仍然是类似的一个问题,但是这道题显得没那么简单,如果就用上面的程序交了是不对的,因为它里面有这么一句话:修改前和修改后每个元素都必须是整数,也就是说不能随便改,那我们怎么办呢
  • 仔细想一下可以知道,如果Data[i+n]-Data[i]<n,那我们无法只通过改变这二者之间的元素达到目的,比如 1 3 2,不可能改变3达到LIS,所以要预先处理位置关系这一问题,我们把Data[i]-=i,这样可以做到Data[i]比Data[i+1]少1,Data[i]比Data[i+2]少2,以此类推,这样得到一个新的数组,只需要找这个数组的最长不下降子序列即可,因为相等的时候原数组位置也是对的
  • 转换成数学语言就是令ci=ai-i,如果a严格递增,那么c一定为非递减
  • 找到最长不下降子序列说明不需要改的位置最多就这些,所以剩下的都要改也就是需要改的最小值
  • 最长不下降子序列和上升子序列之间只差了一个等号
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
#include <sstream>
using namespace std;
typedef long long ll;
const int MAXN = 2e5+100;
int Data[MAXN];
int f[MAXN];
int solve(int n){
    
    
    f[1] = Data[0];
    int len = 1;
    for(int i=1;i<n;i++){
    
    
        if(Data[i]>=f[len]) f[++len] = Data[i];
        else{
    
    
            int pos = upper_bound(f+1,f+len+1,Data[i]) - f;
            f[pos] = Data[i];
        }
    }
    return len;
}
int main(){
    
    
    int n,t;
    cin>>t;
    int tt = 0;
    while(t--){
    
    
        tt++;
        scanf("%d",&n);
        for(int i=0;i<n;i++){
    
    
            scanf("%d",&Data[i]);
            Data[i]-=i;
        }
        cout<<"Case #"<<tt<<':'<<endl;
        cout<<n-solve(n)<<endl;
    }
    return 0;
}

接下来的一个问题
题目链接
这道题更加深入,细节也更多,题目大意是给你两个数组,第一个是一个序列,第二个数组是这些序列的某些位置,这个数组已经是升序排列,这些位置是锁定的不能改,第二个数组也可能是空的,让你找最少需要改多少元素能让第一个数组变成LIS

  • 锁定了某些元素就相当于把原数组分成了若干份,直观的想法就是把这些份每一个都去按照HDU那道题求一下需要改的元素个数,再加和,就得到答案,如何判-1?还是HDU那道题的理解,如果转换后的数组后一项比前一项小,就说明不能得到答案
  • 还需要注意的一点是,例如样例一,前三个数是1 2 1,第三个位置锁定,所以前两个数都需要改,这个可以通过在形成的最长不下降子序列中寻找小于等于a[high]的第一个出现的数的位置来实现,比如上面这个例子,从下标1开始,转换之后得到的数组是0 0 -2,那么小于等于-2的第一个出现的数是-2,位置是3,所以他前面的数都要改,有3-1=2个数
  • 在两个数组的头和尾都加上哨兵,确保能把每个元素都包含进来
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
const int MAXN = 5e5+100;
int a[MAXN];
int b[MAXN];
int f[MAXN];
bool check(int k){
    
    
    for(int i=2;i<=k;i++){
    
    
        if(a[b[i-1]] > a[b[i]]) return false;
    }
    return true;
}
int solve(int low,int high){
    
    
    int len = 1;
    f[1] = a[low];
    for(int i=low + 1;i<=high;i++){
    
    
        if(a[i] >= f[len]) f[++len] = a[i];
        else{
    
    
            int pos = upper_bound(f+1,f+len+1,a[i]) - f;
            if(pos!=1) f[pos] = a[i];
        }
    }
    int pos = upper_bound(f+1,f+len+1,a[high]) - f - 1;
    return high - low + 1 - pos;
}
int main(){
    
    
    int n,k;
    cin>>n>>k;
    a[0] = -0x3f3f3f3f;
    for(int i=1;i<=n;i++){
    
    
        cin>>a[i];
        a[i] -= i;
    }
    a[n+1] = 0x3f3f3f3f;
    int ans = 0;
    b[0] = 0;
    for(int i=1;i<=k;i++) cin>>b[i];
    b[k+1] = n+1;
    if(!check(k)) cout<<"-1"<<endl;
    else{
    
    
        for(int i=1;i<=k+1;i++){
    
    
            ans += solve(b[i-1],b[i]);
        }cout<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/roadtohacker/article/details/112003749