最长上升子序列模型

Posted Debroon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最长上升子序列模型相关的知识,希望对你有一定的参考价值。

最长上升子序列模型

 


前置知识:最长上升子序列

AcWing 1017. 怪盗基德的滑翔翼

最优解,是某个起点的最长下降子序列。

  • 最长下降子序列的状态是一维的 dp[n]
  • 起点是一个变量,需要每个顶点都试一次
  • 这个题目的最长下降子序列,不止可以从左边走,还可以从右边走,所以俩边都需要做一次
int res = 0;
for (int i = 0; i < n; i ++ )     	      // 向右边遍历,枚举所有顶点作为起始点
	f[i] = 1;                      	      // 最简单的情况,长度是自己
	for (int j = 0; j < i; j ++ )  		  // 考虑 f[i] 前面的数,可不可以
		if ( h[i] < h[j] )                // 保证是下降的
			f[i] = max(f[i], f[j] + 1);   // f[i] 从哪里来?自身 或 前面的 + 1
		res = max(res, f[i]);             // 向右走的最长下降子序列


memset(f, 0, sizeof f);
for (int i = n - 1; i >= 0; i -- )       // 向左边遍历,原理同上
	f[i] = 1;
	for (int j = n - 1; j > i; j -- )
		if ( h[i] < h[j])
			f[i] = max(f[i], f[j] + 1);
		res = max(res, f[i]);

 


AcWing 1014. 登山


登山的限制:

  • 上山,只能向上爬
  • 下山,只能向下走
  • 走过尽可能多的景点

问题就变成了:最长上升子序列 + 最长下降子序列

  • 细节:需要 - 1,因为最长上升子序列的终点和最长下降子序列的起点重合了
	for (int i = 0; i < n; i ++ )
    
        f[i] = 1;
        for (int j = 0; j < i; j ++ )
            if (h[i] > h[j])
                f[i] = max(f[i], f[j] + 1);
    

    for (int i = n - 1; i >= 0; i -- )
    
        g[i] = 1;
        for (int j = n - 1; j > i; j -- )
            if (h[i] > h[j])
                g[i] = max(g[i], g[j] + 1);
    

    int res = 0;
    for (int i = 0; i < n; i ++ ) res = max(res, f[i] + g[i] - 1);
    return res;

 


AcWing 482. 合唱队形


合唱队形 = 上升子序列 + 下降子序列 - 1


这题目看了几遍没看懂。

  • 就是卡在,最少需要几位同学出列,才能让剩下的同学排成合唱队形。

最少出列,意味着让合唱队形最长。

  • 问题是:合唱队形最长是多少?

最长合唱队形 = 最长上升子序列 + 最长下降子序列 - 1

代码和上一题没有区别。

  • 上一题,返回 res
  • 这一题,返回 n - res
     

AcWing 1012. 友好城市


题目:建最多的航线

  • 俩岸的友好城市才能建一条
  • 航线不能交叉


最关键的抓手是,不能相交是啥意思呢?

  • 把不能相交,转化为上升子序列
  • 把最多不相交,转化为最长上升子序列

我们按照自变量顺序排序,不相交序列如下:

相交序列如下:

我们将一边的城市坐标从小到大排序,可以发现令航线不交错的方法即,为找到另一边的城市坐标的最长上升子序列。

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

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

int main() 
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d %d", &city[i].first, &city[i].second);
    sort(city, city + n);

    int res = 0;
    for (int i = 0; i < n; i ++ ) 
        f[i] = 1;
        for (int j = 0; j < i; j ++ )
            if (city[i].second > city[j].second)
                f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
    

    printf("%d\\n", res);
    return 0;

 


AcWing 1016. 最大上升子序列和


最长上升子序列的和,从哪里来?

  • 最简单的情况:f[0] = w[0],等于自身权重
  • f[n] 从哪里来:max(f[n], f[前面的某数]+w[n])

逻辑和最长子序列,没什么不同,只是改一下初始值、权重。

  • 但和最长上升子序列不同的是,最长上升子序列的和最终结果不是 f[n] 最大

比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。

所以,我们在每轮循环后,还需要选出当前轮的最大值和下一轮最大值比较。

一组擂台,只能选出一组的最大值。

N组擂台,需要把每组的最大值再来打一次,才能选出最终的最大值。

int res = 0;
for (int i = 0; i < n; i ++ ) 
	f[i] = w[i];
	for (int j = 0; j < i; j ++ )
		if (w[i] > w[j])
			f[i] = max(f[i], f[j] + w[i]);     
    res = max(res, f[i]);                      // 最终的最大值擂台

 


AcWing 1010. 拦截导弹


这题比较特别:

  • 第一问:这套系统最多能拦截多少导弹,就是求一个最长上升子序列。
  • 第二问:拦截所有导弹最少要配备多少套系统,就是用最少的最长上升子序列覆盖。

我们用贪心思路想,就是每个子序列越大越好,每个都是最长上升子序列,就会最少。

  • 当然贪心策略是很简单的,主要是证明难。
  • 局部最优解是否等于全局最优解呢?
  • 等于,具体证明我也不会,省略。
  • 数学好的大佬,可以来写下。

贪心流程,从前往后扫描每个数,对于每个数:

  • 情况1:如果现有的子序列的结尾都小于当前数,则只能创建新的子序列,子序列数量+1。
  • 情况2:将当前数放到结尾大于等于它的最小的子序列后面,组成子序列长度+1。

举个例子,现在选择了 389,形成系统 1,后面的 207 有俩种选择:

发现了吗?这个思想不就是贪心接最长的上升子序列吗?

第二问的贪心做法:

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

using namespace std;

const int N = 1010, INF = 30010;

int n, x;
int w[N], f[N];
int q[N], cnt;

int main()

    while (cin >> x) w[ ++ n] = x;
    //对于第一问,先做一遍最长上升子序列模型DP
    int res = 0;
    w[0] = INF;
    for (int i = 1; i <= n; ++ i)
    
        for (int j = 0; j < i; ++ j)
        
            if (w[j] >= w[i]) f[i] = max(f[i], f[j] + 1);
        
        res = max(res, f[i]);
    
    cout << res << endl;

    //对于第二问,我们采用题解中所说的贪心思路
    for (int i = 1; i <= n; ++ i)
    
        int l = 0, r = cnt; //二分出插入的区间左端点
        while (l < r)
        
            int mid = l + r >> 1;
            if (q[mid] >= w[i]) r = mid;    //右边的性质满足q[k] >= w[i]
            else l = mid + 1;
        
        if (q[r] < w[i]) r ++ ; //处理边界,当没有能够满足当前高度的系统时,再开一个
        cnt = max(cnt, r);
        q[r] = w[i];
    
    cout << cnt << endl;
    return 0;

 


AcWing 187. 导弹防御系统

 


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

 


以上是关于最长上升子序列模型的主要内容,如果未能解决你的问题,请参考以下文章

最长上升子序列模型

合唱队形(DP)

Luogu P1091 合唱队形

最长上升子序列

合唱队形

最长上升子序列