1.2.2最长上升子序列模型(二)

news2024/11/15 21:30:52

1.拦截导弹

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于 30000 30000 30000的正整数,导弹数不超 1000 1000 1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式
共一行,输入导弹依次飞来的高度。

输出格式
第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围
雷达给出的高度数据是不大于 30000 30000 30000的正整数,导弹数不超过 1000 1000 1000

输入样例:

389 207 155 300 299 170 158 65

输出样例:

6
2

题解

Dilworth 定理

狄尔沃斯定理(Dilworth’s theorem)亦称偏序集分解定理,该定理断言:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。此定理的对偶形式亦真,它断言:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目

该定理在子序列问题上可表述为:把序列分成不升子序列的最少个数,等于序列的最长上升子序列长度。把序列分成不降子序列的最少个数,等于序列的最长下降子序列长度

该定理在二分图上等价于柯尼希定理:二分图最小点覆盖的点数等于最大匹配数

定理法
给定正整数序列,求最长不升子序列长度,以及能覆盖整个序列的不升子序列的最少个数

问题一套用 LIS模型即可求解。由 Dilworth 定理,问题二等价于 LIS长度

#include <cstdio>

#define max(a, b) ((a) > (b) ? (a) : (b))
#define rep(i, s, e) for (int i = s; i <= e; i ++)
#define N 1010

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

int main() {
    int n = 1; while (~scanf("%d", a + n)) n ++; n --;

    rep(i, 1, n) rep(j, 0, i - 1) {
        bool down = a[j] >= a[i];
        f[i] = max(f[i], down * f[j] + 1);
        g[i] = max(g[i], !down * g[j] + 1);
    }

    int r1 = 0, r2 = 0;
    rep(i, 1, n) r1 = max(r1, f[i]), r2 = max(r2, g[i]);

    printf("%d\n%d\n", r1, r2);

    return 0;
}
贪心法

在这里插入图片描述
对于每个数,既可以把它接到已有子序列后面,也可以建立一个新序列。要使子序列数最少,应尽量不建立新序列。此外,应让每个子序列的末尾尽可能大,这样能接的数更多。因为一个数若能接到小数后面,必然能接到大数后面,反之则不成立。根据这些想法,可总结出如下贪心流程:

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

  1. 若现有子序列的结尾都小于它,则创建新子序列
  2. 否则,将它放到结尾大于等于它的最小数后面

证明

A A A为贪心解, B B B为最优解

  1. 贪心解能覆盖所有数,且形成的都是不升序列,因此合法。由定义, B ≤ A B≤A BA
  2. 假设最优解对应的方案和贪心方案不同,从前往后找到第一个不在同一序列的数 x x x。假设贪心解中 x x x前面的数是 a a a,最优解中 x x x 前面的数是 b b b a a a 后面的数是 y y y,由于贪心会让当前数接到大于等于它的最小数后面,所以 x , y ≤ a ≤ b x,y≤a≤b x,yab,此时,在最优解中,把 x x x 一直到序列末尾,和 y y y 一直到序列末尾交换位置,这样做不影响正确性,也不增加序列个数,但会使 x x x 在最优解和贪心解中所处的位置相同。由于序列中的数是有限的,只要一直做下去,一定能使最优解变为贪心解。因此 A ≤ B A≤B AB

综上 A = B A=B A=B

实现
g g g保存每条不升子序列的末尾,可用归纳法证明 g g g是单调上升的。初始时, g g g为空满足条件。假设 g g g已经单调上升,现在要加入数 x x x,设 g [ i ] g[i] g[i]是大于等于 x x x的最小数,则 g [ i − 1 ] < x ≤ g [ i ] ≤ g [ i + 1 ] g[i−1]<x≤g[i]≤g[i+1] g[i1]<xg[i]g[i+1]。由于 x x x要放到 g [ i ] g[i] g[i] 后面,因此 g [ i ] g[i] g[i] 会被更新成 x x x,之后满足 g [ i − 1 ] < g [ i ] ≤ g [ i + 1 ] g[i−1]<g[i]≤g[i+1] g[i1]<g[i]g[i+1],其它元素不受影响,整个序列仍单调上升。由此得证

可发现上述贪心实现,和最长上升子序列的贪心解法代码相同,这也印证了 Dilworth 定理

#include <cstdio>

#define max(a, b) ((a) > (b) ? (a) : (b))
#define rep(i, s, e) for (int i = s; i <= e; i ++)
#define N 1010

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

int main() {
    int n = 1; while (~scanf("%d", a + n)) n ++; n --;

    rep(i, 1, n) rep(j, 0, i - 1)
        f[i] = max(f[i], (a[j] >= a[i]) * f[j] + 1);

    int r = 0; rep(i, 1, n) r = max(r, f[i]);
    printf("%d\n", r);

    r = 0;
    rep(i, 1, n) {
        int j = 0; while (j < r && g[j] < a[i]) j ++;
        g[j] = a[i]; r += j == r;
    }
    printf("%d\n", r);

    return 0;
}

y总做法

#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;//k是从前往后找的序列
        while(k < cnt && g[k] < q[i]) k++;
        g[k] = q[i];
        if(k >= cnt)cnt++;
    }
    cout<<cnt<<endl;
    
    return 0;
}

2.导弹防御系统

为了对抗附近恶意国家的威胁,R国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 3 3 3和高度为 4 4 4的两发导弹,那么接下来该系统就只能拦截高度大于 4 4 4的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入格式
输入包含多组测试用例。

对于每个测试用例,第一行包含整数 n n n,表示来袭导弹数量。

第二行包含 n n n不同的整数,表示每个导弹的高度。

当输入测试用例 n = 0 n=0 n=0 时,表示输入终止,且该用例无需处理。

输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。

数据范围
1 ≤ n ≤ 50 1≤n≤50 1n50
输入样例:

5
3 5 2 4 1
0 

输出样例:

2

样例解释
对于给出样例,最少需要两套防御系统。

一套击落高度为 3 , 4 3,4 3,4 的导弹,另一套击落高度为 5 , 2 , 1 5,2,1 5,2,1 的导弹。

题解

  • 概述

该题也是一个典型的最长上升子序列的问题。

条件: 导弹拦截高度要么一直上升要么一直下降。

有的导弹可以选择上升,有的可以选择下降,不是单纯地问所存在的序列可以划分为多少组上升子序列的问题,所以不能用之前的方法解。

怎么做?
当找不到办法时,考虑使用枚举法

如何做?
从问题的解出发,最终问题的答案是有许多单调上升子序列和许多单调下降子序列,那么实际就是对于每个数,来思考将该数放到上升序列中还是下降序列中。

  • 题解

注意顺序性
  • 依次枚举每个数
    先枚举将该数作为单调上升的序列,还是单调下降的序列中

    • 如果该数被放到了单调上升的序列中,则枚举将该数放到哪个单调上升的序列后面
    • 如果该数被放到了单调下降的序列中,则枚举将该数放到哪个单调下降的序列后面。
    • 或者该数成为一个单独的单调序列

    实际上来看这就是一个建模问题,首先建模为初始状态,初始状态可以经过很多离散步骤到达一个新的状态,最终不断地到达目的状态。

暴力,考虑搜索空间,首先如果搜索的序列是有序的,那么就可以使用二分法。每一步搜索空间是50,那么这样下去,就是5050…, 最终是指数级别

up[] 存储当前所有上升子序列的末尾元素
down[] 存储当前所有下降子序列的末尾元素

所以这里实际上用到了上一题导弹防御的结果来做,对于多个单调上升(下降)子序列只需要对末尾元素进行考虑即可。
这一点真厉害!!!
然后这里又可以进行优化

使用贪心的思想
x x x 尽可能覆盖一个末尾元素大的序列

因此是一个很强的优化,所以每一步实际上只有两种选择,要么上升,要么下降。

up本身是单调的,所以一方面找到即停止,另一方面可以直接考虑用二分

如何进行搜素

  1. 使用BFS, BFS是宽度优先,需要进行存储(空间太多,不太好剪枝),而且代码写起来比较麻烦
  2. 使用DFS, 一种想法是使用全局变量,第二种方式是迭代加深

代码实现

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 55;

int n;
int q[N];
int up[N],down[N];//一个表示上升子序列结尾,另外一个表示下降子序列结尾
int ans;

//su表示当前上升子序列个数,sd表示当前下降子序列个数
void dfs(int u,int su,int sd)
{
    if(su + sd >= ans) return;
    if(u == n)
    {
        ans = su + sd;
        return;
    }
    //情况1:将当前数放到上升子序列中
    int k = 0;
    while(k <= su && up[k] >= q[u]) k++;
    int t = up[k];
    up[k] = q[u];
    if(k < su)  dfs(u + 1,su,sd);
    else dfs(u + 1,su + 1,sd);
    up[k] = t;
    
    //情况2:将当前数放到下降子序列中
    k = 0;
    while(k < sd && down[k] <= q[u])    k++;
    t = down[k];
    down[k] = q[u];
    if(k < sd)  dfs(u + 1,su,sd);
    else dfs(u + 1,su,sd + 1);
    down[k] = t;
}
int main()
{
    while(cin >> n,n)
    {
        for(int i = 0;i < n;i++)    cin >> q[i];
        
        ans = n;
        
        dfs(0,0,0);
        
        cout << ans << endl;
    }
    return 0;
}

3.最长公共上升子序列

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列 A A A B B B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列 A A A B B B的长度均不超过 3000 3000 3000

输入格式
第一行包含一个整数 N N N,表示数列 A , B A,B AB 的长度。

第二行包含 N N N 个整数,表示数列 A A A

第三行包含 N N N 个整数,表示数列 B B B

输出格式
输出一个整数,表示最长公共上升子序列的长度。

数据范围
1 ≤ N ≤ 3000 1≤N≤3000 1N3000,序列中的数字均不超过 2 31 − 1 2^{31}−1 2311

输入样例:

4
2 2 1 3
2 1 2 3

输出样例:

2

题解(此题不是很懂,比较综合)在这里插入图片描述

(DP,线性DP,前缀和) O ( n 2 ) O(n^{2}) O(n2)

这道题目是AcWing 895. 最长上升子序列和AcWing 897. 最长公共子序列的结合版,在状态表示和状态计算上都是融合了这两道题目的方法。此两道题在之前博客均有叙述,同时可以在Acwing官网查看(算法提高课)

状态表示:

  • f[i][j]代表所有a[1 ~ i]b[1 ~ 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

如果直接按上述思路实现,需要三重循环:

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])
        {
            int maxv = 1;
            for (int k = 1; k < j; k ++ )
                if (a[i] > b[k])
                    maxv = max(maxv, f[i - 1][k] + 1);
            f[i][j] = max(f[i][j], maxv);
        }
    }
}

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

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

C++ 代码

#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/817576.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最全的3D动画软件介绍来了!良心总结9款3D动画制作必备软件

现在&#xff0c;市面上流行着的3D动画软件如此之多&#xff0c;以至于很难敲定到底哪一款更适合自己或自己的团队。本篇文章带来了一些热门的、被视为行业标准的3D动画软件的介绍&#xff0c;帮助您更好地做出选择。 不仅如此&#xff0c;您还能从文章中了解到在数字内容创建…

移动应用项目架构设计

一、项目整体架构设计 按照分层的架构思想&#xff0c;从下到上分为ShareLib(共享库)层、Core(核心功能)层、Business(业务逻辑)层、App(应用展示)层。 架构优势&#xff1a; 严格控制各层的职责范围&#xff0c;使得底层库实现工程间复用且无缝替换&#xff0c;上层业务无感知…

垃圾焚烧设备PLC数据采集远程监控系统解决方案

PLC可以应用于各种污染废物处理设备的自动化控制&#xff0c;如污水处理、垃圾焚烧、空气处理等。例如&#xff0c;通过对垃圾焚烧PLC设备的数据采集&#xff0c;可以实现对垃圾焚烧的温度、时间、氧气流量等数据的远程监控和实时预警&#xff0c;有效提高垃圾焚烧效率和环保效…

使用机器学习进行疾病预测 -- 机器学习项目基础篇(3)

本文旨在实现一个强大的机器学习模型&#xff0c;可以根据他/她所拥有的症状有效地预测人类的疾病。让我们看看如何解决这个机器学习问题&#xff1a; 方法&#xff1a; 收集数据&#xff1a;数据准备是任何机器学习问题的主要步骤。我们将使用来自Kaggle的数据集来解决这个问…

C语言——静态库和动态库的创建和使用

使用库函数是源码的一种保护 库函数其实不是新鲜的东西,我们一直都在用,比如C库。我们执行pringf() 这个函数的时候,就是调用C库的函数. 下面记录静态库和动态库的生成和使用. 静态库:libxxx.a 动态库:libxxx.so 静态库: 在程序编译的时候,将库编译进可执行程序中, 运行的…

【Unity学习笔记】生命周期

文章目录 脚本的生命周期初始化更新顺序动画更新循环各类事件结束阶段 阶段分析协程返回 总结 官方文档&#xff1a;事件函数的执行顺序 脚本的生命周期 如图&#xff1a; 脚本的生命周期主要经历以下几个阶段&#xff1a; 初始化 初始化阶段&#xff0c;&#xff08;包括初…

RISC-V 指令集介绍

1. 背景介绍 指令集从本质上可以分为复杂指令集&#xff08;Complex Instruction Set Computer&#xff0c;CISC&#xff09;和精简指令集&#xff08;Reduced Instruction Set Computer&#xff0c;RISC&#xff09;两种。复杂指令集的特点是能够在一条指令内完成很多事情。 指…

1400*A. Factory

Examples input 1 5 output No input 3 6 output Yes 题意&#xff1a; a 和 m&#xff0c;a 不断加 a%m &#xff0c;如果 a 有一次能够被 m 整除&#xff0c;则打印 Yes&#xff0c;如果一直循环永远不可能被 m 整除&#xff0c;则打印 No 解析&#xff1a; 可以观…

BUU CODE REVIEW 1

BUU CODE REVIEW 1 考点&#xff1a;PHP变量引用 源码直接给了 <?phphighlight_file(__FILE__);class BUU {public $correct "";public $input "";public function __destruct() {try {$this->correct base64_encode(uniqid());if($this->c…

数据库中RIGHT_TYPE=3和RIGHT_TYPE=“3“的区别

G_USERINFO表归档条件SQL错误&#xff0c; RIGHT_RIGHT_TYPE3应改为RIGHT_TYPE"3"&#xff0c; 该字段为字符类型 在Oracle中&#xff0c;类型&#xff08;type&#xff09;通常是指数据库中的表、视图、索引、存储过程、函数等对象的的数据类型或结构。在比较类型&a…

解决:h5的<video>在移动端浏览器无法自动播放

并不是所有的移动端浏览器都无法自动播放&#xff0c;下载谷歌、火狐、edge等都可以正常播放&#xff0c;目前发现夸克浏览器无法自动播放。即autoplay属性失效。 <video autoplay"autoplay"></video> 可能移动端有移动端的策略&#xff0c;但解决夸克…

C++标识符的作用域与可见性

标识符是一个由程序员定义的名称&#xff0c;为标识区别变量、函数和数据类型等&#xff0c;代表程序的某些元素&#xff0c;变量名就是标识符的一个展现。 作用域讨论的是标识符的有效范围&#xff0c;可见性讨论的是标识符是否可以被引用。在一个函数中声明的变量就只能在这个…

vue中使用代码编辑器 vue2-ace-editor

npm install --save-dev vue2-ace-editor// 全局引入 main.jsimport Editor from vue2-ace-editor; Vue.use(Editor)//组件中引入import Editor from vue2-ace-editor; components: {Editor, },<template><div class"codeEditBox"><editorv-model&qu…

2353. 设计食物评分系统;1895. 最大的幻方;842. 将数组拆分成斐波那契序列

2353. 设计食物评分系统 核心思想&#xff1a;首先明确我们有哪些功能&#xff0c;首先是修改某种食物分数的功能&#xff0c;然后第二点是能够每次弹出分数高字典序小的食物名字。由这两个我们想到了a 食物[分数],b 烹饪方式[分数&#xff0c;食物名字] 然后有一点经验的感…

小程序基础笔记

一、小程序与普通网页开发的区别 1、运行环境不同 网页运行在浏览器环境中小程序运行在微信环境中 2、API 不同 由于运行环境的不同&#xff0c;所以小程序中&#xff0c;无法调用 DOM 和 BOM 的 API。但是&#xff0c;小程序中可以调用微信环境提供的各种 API&#xff0c;例如…

初识低代码开发平台

2019年开始&#xff0c;低代码爆火&#xff0c;有人唱衰有人捧&#xff0c;反对的人认为对于那些真正的程序员来说&#xff0c;简直就是毒瘤&#xff0c;只是炒作概念而已&#xff0c;等尘埃落地&#xff0c;肯定一地鸡毛。 但是对于那些缺技术、缺人才&#xff0c;又需要数字…

ERROR:No tf data. Actual error: Fixed Frame [map] does not exist 解决办法

问题场景&#xff1a; 使用rviz时&#xff0c;出现warning&#xff0c;并且地图无法加载&#xff0c;如下所示&#xff1a; 原因分析&#xff1a; 之所以地图无法加载出来&#xff0c;其主要原因是tf树中没有world坐标系&#xff0c;解决方法就是让rviz知道world坐标系在哪…

短视频平台视频怎么去掉水印?

短视频怎么去水印&#xff0c;困扰很多人&#xff0c;例如&#xff0c;有些logo水印&#xff0c;动态水印等等&#xff0c;分享操作经验&#xff1a; 抖音作为中国最受欢迎的社交娱乐应用程序之一&#xff0c;已成为许多人日常生活中不可或缺的一部分。在使用抖音过程中&#x…

《TCP IP网络编程》第十四章

第 14 章 多播与广播 14.1 多播 多播&#xff08;Multicast&#xff09;方式的数据传输是基于 UDP 完成的。因此 &#xff0c;与 UDP 服务器端/客户端的实现方式非常接近。区别在于&#xff0c;UDP 数据传输以单一目标进行&#xff0c;而多播数据同时传递到加入&#xff08;注…

【奥比中光Gemini 2L快速上门】

奥比中光Gemini 2L快速上手 目录 奥比中光Gemini 2L快速上手[TOC](目录) 一、下载配置环境1.1 官网下载SDK1.2 配置环境 二、测试2.1 在bin中运行示例2.2 配置cmake 三、CMAKE3.1 CmakeLists.txt中各设置的意义 一、下载配置环境 1.1 官网下载SDK 进入官网&#xff0c;下载名…