【动态规划】—— 最长上升子序列模型


AcWing 1017. 怪盗基德的滑翔翼

输入样例:

3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10

输出样例:

6
6
9

当确定完方向起点完后,最长的距离是什么呢?

起点:a[i]

最长:以 a[i] 为起点的最长上升子序列 (LIS)


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

const int N = 110;

int n;
int a[N], f[N];

int main()
{
    int T;
    cin >> T;
    while(T -- )
    {
        cin >> n;
        for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
        
        // 正向求解 LIS
        int res = 0;
        for(int i = 1; i <= n; i ++ )
        {
            f[i] = 1;
            for(int j = 1; j < i; j ++ )
                if(a[i] > a[j])
                    f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }
        
        // 反向求解 LIS
        for(int i = n; i; i -- )
        {
            f[i] = 1;
            for(int j = n; j > i; j -- )
                if(a[i] > a[j])
                    f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }
        
        cout << res << endl;
    }
    return 0;
}

AcWing 1014. 登山 

输入样例:


输出样例:

4

题目中有以下条件

  1. 按照编号递增的顺序来浏览(子序列)
  2. 相邻的两个景点不能相同高度(严格单调)
  3. 一旦开始下降就不能上升 

目标:求最多能浏览多少景点

两个LIS然后求解出相加最大值


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

const int N = 1010;

int n;
int a[N];
int f[N], g[N];


int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    for(int i = 1; i <= n; i ++ )
    {
        f[i] = 1;
        for(int j = 1; j < i; j ++ )
            if(a[i] > a[j])
                f[i] = max(f[i], f[j] + 1);
    }
    
    for(int i = n; i; i -- )
    {
        g[i] = 1;
        for(int j = n; j > i; j -- )
            if(a[i] > a[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;
}

AcWing 1012. 友好城市

输入样例:

7
22 4
2 6
10 3
15 12
9 8
17 17
4 2

输出样例:

4

 所有合法的建桥方式都对应着一个因变量的上升子序列

 因此,该方案中,在上坐标排序的情况下,下坐标次序不是从小到大的,则必然不合法(会有相交)

于是,这题就变成了:桥以上坐标从小到大排序后,找出下坐标的最长上升子序列长度


#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5010;

typedef pair<int, int> PII;

int n;
PII q[N];
int f[N];

int main()
{
    cin >> n;
    for(int i = 0; i < n; i ++ ) scanf("%d%d", &q[i].first, &q[i].second);
    sort(q, q + n);
    
    int res = 0;
    for(int i = 0; i < n; i ++ )
    {
        f[i] = 1;
        for(int j = 0; j < i; j ++ )
            if(q[i].second > q[j].second)
                f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
    }
    
    cout << res << endl;
    
    return 0;
}

AcWing 1016. 最大上升子序列和 

输入样例:

7
1 7 3 5 9 4 8

输出样例:

18


 

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

const int N = 1010;

int n;
int a[N], f[N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    
    for(int i = 1; i <= n; i ++ )
    {
        f[i] = a[i];
        for(int j = 1; j < i; j ++)
            if(a[j] < a[i])
                f[i] = max(f[i], f[j] + a[i]);
    }
    int res = 0;
    for(int i = 1; i <= n; i ++ ) res = max(res, f[i]);
    cout << res << endl;
    return 0;
}

AcWing 1010. 拦截导弹 

输入样例:

389 207 155 300 299 170 158 65

输出样例:

6
2

这道题的第一问是一个经典的 LIS 问题

对于第二问,我们采用贪心思路

贪心流程:

从前往后扫描每个数,对于每个数:

        情况1:如果现有的子序列的结尾都小于当前数,则创建新的子序列

        情况2:将当前数放到大于等于它的最小的子序列后面

贪心证明:

1.如何证明两个数相等? A\geq B,A\leq B

 A 表示贪心算法得到的序列个数,B 表示最优解

 ① B\leq A,由最优解的性质可得

 ② A\leq B 调整法

        假设最优解对应的方案和当前方案不同

        找到第一个不同数。

        通过有限次的交换可以将最优解调整为贪心解,且不增加序列个数


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

const int N = 1010;

int n;
int q[N];
int f[N], g[N];

int main()
{
    while(cin >> q[n]) n ++ ;
    
    int res = 0;
    for(int i = 0; i < n; i ++ )
    {
        f[i] = 1;
        for(int j = 0; j < i; j ++ )
            if(q[j] >= q[i])
                f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
    }
    
    cout << res << endl;
    
    int cnt = 0;
    for(int i = 0; i < n; i ++ )
    {
        int k = 0;
        while(k < cnt && g[k] < q[i]) k ++ ;
        g[k] = q[i];
        if(k >= cnt) cnt ++ ;
    }
    
    cout << cnt << endl;
    
    return 0;
}

 AcWing 272. 最长公共上升子序列

4
2 2 1 3
2 1 2 3

输出样例:

2


这道题目是最长上升子序列最长公共子序列的结合版,在状态表示和状态计算上都是融合了这两道题目的方法。

状态表示:

f[i][j] 代表所有 a[1 ~ i] 和 b[i ~ j] 中以 b[j] 结尾的公共上升子序列的集合

f[i][j]的值等于该集合的子序列中长度的最大值 

状态计算:

首先根据公共子序列中是否包含a[i],将 f[i][j] 所代表的集合分成两个不重不漏的子集

  • 不包含 a[i] 的子集,最大值是 f[i - 1][j]
  • 包含a[i] 的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在 b[] 中是哪个数
    • 子序列只包含 b[j] 一个数,长度是1
    • 子序列的倒数第二个数是 b[1] 的集合,最大长度是 f[i-1][1] + 1
    • ....
    • 子序列的倒数第二个数是 b[j - 1] 的集合,最大长度是 f[i-1][j-1] + 1

得到以下O(n^3) 的做法

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    for(int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);
    
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= n; j ++ )
        {
            f[i][j] = f[i - 1][j];
            if(a[i] == b[j])
            {
                f[i][j] = max(f[i][j], 1);
                for(int k = 1; k < j; k ++ )
                    if(b[k] < b[j])
                        f[i][j] = max(f[i][j], f[i][k] + 1);
            }
        }
        
    int res = 0;
    for(int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
    
    cout << res << endl;
    
    return 0;
}

然后我们发现每次循环求得的maxv是满足a[i] > b[k]的f[i - 1][k] + 1的前缀最大值。
因此可以直接将maxv提到第一层循环外面,减少重复计算,此时只剩下两重循环。

最终答案枚举子序列结尾取最大值即可。

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);

    for (int i = 1; i <= n; i ++ )
    {
        int maxv = 1;
        for (int j = 1; j <= n; j ++ )
        {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
            if (a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);
        }
    }

    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
    printf("%d\n", res);

    return 0;
}
  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玄澈_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值