最长非递减子序列(基础题(二))

给定一个长度为 nn 的数字序列 a1,a2,…,an,序列中只包含数字 1 和 2。

现在,你要选取一个区间 [l,r](1≤l≤r≤n),将 al,al+1,…,ar 进行翻转,并且使得到的新数字序列 a的最长非递减子序列的长度尽可能长。

请问,这个最大可能长度是多少?

一个非递减子序列是指一个索引为 p1,p2,…,pk 的序列,满足 p1<p2<…<pk 并且 ap1≤ap2≤…≤apk,其长度为 k。

输入格式

第一行一个整数 n。

第二行 n 个空格隔开的数字 1或 2,表示 a1,…,an

输出格式

输出一个整数,表示得到的新数字序列 aa 的最长非递减子序列的最大可能长度。

数据范围

对于 30的数据,1≤n≤100
对于 100的数据,1≤n≤106。
本题读入数据规模较大,需注意优化读入。

输入样例1:
4
1 2 1 2
输出样例1:
4
输入样例2:
10
1 1 2 2 2 1 1 2 2 1
输出样例2:
9

首先如果是求序列12的长度,那么

1111111111…1111111111…
– 只可能由 111111…111111… 这种状态转移而来
111111112222222222…111111112222222222…
– 可能由 111111…111111… 转移而来
– 也可能由 111112222…111112222… 转移而来

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

int main() {
    int n;
    cin >> n;
    int s1 = 0, s2 = 0;
    while (n--) {
        int x;
        cin >> x;
        if (x == 1) {
            s1++; // 如果当前数字是 1,则状态1的长度加1
        } else {
            s2 = max(s1 + 1, s2 + 1); // 如果当前数字为2,可能由两种状态转移而来
        }
    }
    cout << max(s1, s2) << endl;
    return 0;
}

回归本题,本题是加了反转

1.试试枚举

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

int main() {
    int s1 = 0, s2 = 0, s3 = 0, s4 = 0;
    int n;
    cin >> n;
    while (n--) {
        int x;
        cin >> x;
        if (x == 1) {
            s1++;
            s3 = max(s2 + 1, s3 + 1);
        } else if (x == 2) {
            s2 = max(s1 + 1, s2 + 1);
            s4 = max(s3 + 1, s4 + 1);
        }
    }
    cout << max(s3, s4) << endl;
    return 0;
}

2.DP

记 f[i][j] 表示只考虑 a1∼ai 的子序列,且子序列的最后一个数在第 j部分的子序列集合。

属性为子序列长度的最大值。

情况 1:ai=1
因为 ai=1,所以 ai 不可能在第 2,4 部分,所以 f[i][2]=f[i−1][2]     f[i][4]=f[i−1][4]。
而 aiai 可以在子序列中第 1,3部分,所以 f[i][1]=f[i−1][1]+1    f[i][3]=max(f[i−1][2],f[i−1][3])+1
情况 2:ai=2
与上面相反,由于人类的本质是复读机,所以很容易把上面的反过来:f[i][1]=f[i−1][1]

f[i][2]=max(f[i−1][1],f[i−1][2])+1

f[i][3]=f[i−1][3]

f[i][4]=max(f[i−1][3],f[i−1][4])+1
综上所述,有

f[i][1]=f[i−1][1]+[ai==1]

f[i][2]=max(f[i−1][1],f[i−1][2])+[ai==2]

f[i][3]=max(f[i−1][2],f[i−1][3])+[ai==1]

f[i][4]=max(f[i−1][3],f[i−1][4])+[ai==2]
复杂度 O(n)。

#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1000005;

int n, x, res;
int f[N][4];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &x);
        f[i][1] = f[i - 1][1] + (x == 1);
        f[i][2] = max(f[i - 1][1], f[i - 1][2]) + (x == 2);
        f[i][3] = max(f[i - 1][2], f[i - 1][3]) + (x == 1);
        f[i][4] = max(f[i - 1][3], f[i - 1][4]) + (x == 2);
    }
    for (int i = 1; i <= 4; ++i) res = max(res, f[n][i]);
    printf("%d\n", res);
    return 0;
}

//滚动数组 空间O(1)
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1000005;

int n, x, res, f[5];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &x);
        f[4] = max(f[3], f[4]) + (x == 2);
        f[3] = max(f[2], f[3]) + (x == 1);
        f[2] = max(f[1], f[2]) + (x == 2);
        f[1] = f[1] + (x == 1);
    }
    for (int i = 1; i <= 4; ++i) res = max(res, f[i]);
    printf("%d\n", res);
    return 0;
}

欢迎评论点赞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值