最长上升子序列模型

在这里插入图片描述

最长上升子序列模型

—— 2023.06,08

核心思想

  • 状态转移思想:第i个位置上的数,有多种选择
    • 一种是不接在前面0 ~ i - 1上满足条件的数后面 : f[i] = 1;
    • 一种是接在0 ~ i - 1上满足条件的数后面 : f[i] = max(f[i],f[j] + 1);

单调性二分优化

  • 核心思想是:用q[i]记录长度为i的最长上升子序列的最小结尾数,二分查找q数组中小于等于a[k]的最大一个数的位置pos,f[k] = pos + 1,再更新q[pos + 1] = a[k],len = max(len,pos + 1),最终的答案就是len;

这样为啥是正确的呢?

  • 首先我们得数组q中存的数一定满足从小到大的单调性;

反证:
假设存在 i < j, q[i] >= q[j],
因为长度为 j 的子序列以 q[j] 结尾,那么一定存在一个数 k < q[j] 使得以 k 结尾的序列长度为 i, 但此时 q[i] > q[j] > k ,
所以 q[i] 应该存的数是 k,与假设矛盾。所以数组q中存的数一定满足从小到大的单调性

  • 其次,因为 q 数组中越大的数,构成的最长上升子序列的长度就更长,以及我们肯定希望a[k]能接在长度最长的子序列后面,所以我们就二分查找q数组中小于等于a[k]的最大一个数的位置pos,f[k] = pos + 1;

  • 接着,为什么要更新q[pos + 1] = a[k]呢?

因为我们二分的时候,找的是小于等于a[k]的最大的一个数,所以更新f[k]的长度为 pos + 1后, 为了维护q数组的性质,q[pos + 1] = a[k];
因为q[pos + 1] > a[k],而f[k] = pos + 1;

  • 最后更新len的长度应该就很好理解了

题目

最长上身子序列 Ⅰ

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1e3+10;
int f[N],a[N];
int n,m;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],f[i]=1;
    for(int i=2;i<=n;i++)
        for(int j=1;j<i;j++) 
            if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
    int mx=0;
    for(int i=1;i<=n;i++) mx=max(f[i],mx);
    cout<<mx<<endl;
    return 0;
}

最长上升子序列 II

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;
int q[N],a[N],len;
int n;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++)
    {
        int l = 0,r = len;
        while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(q[mid] < a[i]) l = mid;
            else r = mid - 1;
        }
        len = max(len,r + 1);
        q[r + 1] = a[i];
    }
    cout << len << endl;
    return 0;
}

怪盗基德的滑翔翼

思路
  • 很显然这是一个最长子序列的题目
  • 题目要求可以选择不同方向,但在同一方向上,只能滑到更低的地方
    • 如果选择右方向相当于从右往左找满足递增的最长子序列
    • 如果选择左方向相当于从右往左找满足递减的最长子序列
  • 所以我们要维护两个数组,一个记录递增的最长子序列,另一个记录递减的最长子序列
代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 110;
int h[N],f[N],g[N]; //f数组统计最长递减子序列,g数组统计最长递增子序列
int t,n;

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> h[i];
    f[1] = 1,g[1] = 1;
    for(int i = 2; i <= n; i++)
    {
        f[i] = g[i] = 1;
        for(int j = 1; j < i; j++)
            if(h[i] < h[j]) f[i] = max(f[i],f[j] + 1);
            else if(h[i] > h[j]) g[i] = max(g[i],g[j] + 1);
    }
    int res = 0;
    for(int i = 1; i <= n; i++) res = max(res,max(f[i],g[i])); //统计答案
    cout << res << endl;
}

int main()
{
    cin >> t;
    while(t--) solve();
    return 0;
}

登山

思路
  • 由题意可知,先上山看风景,再下山看风景,下山后,不再上山;
  • 所以我们要找的是(以第i个位置举例)
    • 从左到右以第i个位置结尾的最长递增子序列的长度 f[i]
    • 从右到左以第i个位置结尾的最长递增子序列的长度 g[i]
  • 然后以i为下山位置,浏览风景区的总数为f[i] + g[i] - 1;
代码
#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010;
int f[N],g[N];
int h[N];
int n;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>h[i],f[i]=1,g[i]=1;
    for(int i=2;i<=n;i++)
        for(int j=1;j<i;j++)
            if(h[i]>h[j]) f[i]=max(f[i],f[j]+1);
    for(int i=n-1;i>=1;i--)
        for(int j=n;j>i;j--)
            if(h[i]>h[j]) g[i]=max(g[i],g[j]+1);
    int res=0;
    for(int i=1;i<=n;i++)  res=max(res,f[i]+g[i]-1);
    cout<<res<<endl;
    return 0;
}

合唱队形

思路

和登山那个题思路一样的;

代码
#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010;
int f[N],g[N];
int h[N];
int n;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>h[i],f[i]=1,g[i]=1;
    for(int i=2;i<=n;i++)
        for(int j=1;j<i;j++)
            if(h[i]>h[j]) f[i]=max(f[i],f[j]+1);
    for(int i=n-1;i>=1;i--)
        for(int j=n;j>i;j--)
            if(h[i]>h[j]) g[i]=max(g[i],g[j]+1);
    int res=n;
    for(int i=1;i<=n;i++)  res=min(res,n-f[i]-g[i]+1);
    cout<<res<<endl;
    return 0;
}

友好城市

思路
  • 用pair<int,int>存了每一对城市之后,排个序
  • 再找pair<int,int>第二个元素递增的最长子序列
代码
#include<iostream>
#include<algorithm>
#define x first
#define y second
using namespace std;

typedef pair<int,int> PII;
const int N=5010;
int f[N];
PII con[N];

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++) scanf("%d%d",&con[i].x,&con[i].y);
    sort(con,con+n);
  //  for(int i=0;i<n;i++) printf("%d %d\n",con[i].x,con[i].y);
    for(int i=0;i<=n;i++)
    {
        f[i]=1;
        for(int j=0;j<i;j++) if(con[i].y>con[j].y) f[i]=max(f[i],f[j]+1);
    }
    int res=0;
    for(int i=0;i<n;i++) res=max(f[i],res);
    cout<<res<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值