当前位置: 首页 > news >正文

【牛客网】dd爱科学 最长非递减子序列 二分查找

题目链接: dd爱科学1.0    牛客竞赛:NC221822

题目描述:

链接:https://ac.nowcoder.com/acm/problem/221822
来源:牛客网题目描述 
大科学家dd最近在研究转基因白菜,白菜的基因序列由一串大写英文字母构成,dd经过严谨的推理证明发现,只有当白菜的基因序列呈按位非递减形式时,这株白菜的高附加值将达到最高,于是优秀的dd开始着手修改白菜的基因序列,dd每次修改基因序列的任意位需要的代价是1
dd想知道,修改白菜的基因序列使其高附加值达到最高,所需要的最小代价的是多少。
输入描述:
第一行一个正整数n(1≤n≤1000000)
第二行一个长度为n的字符串,表示所给白菜的基因序列
保证给出字符串中有且仅有大写英文字母
输出描述:
输出一行,表示最小代价
示例1
输入
复制
5
ACEBF
输出
复制
1
说明
改成ACEEF或者ACEFF,都只用改动一个字符,所需代价最小为1

最长非递子序列LIS

  1. 非递减:序列中每个元素大于等于前一个元素(如 A≤C≤E≤F2≤2≤3)。
  2. 子序列:从原序列中按原有顺序选取部分元素组成的新序列(元素可间隔,无需连续)。例如原序列 ACEBFA、C、F 是子序列,A、B、F 也是子序列,但 C、A 不是(顺序颠倒)。

LIS 定义:在原序列的所有非递减子序列中,长度最长的那一个(或多个,LIS 可能不唯一,但长度固定)。

二、实例解析:以基因序列 ACEBF 为例

原序列:A C E B F(对应字母序数字:0 2 4 1 5),我们分两步理解 LIS:

步骤 1:暴力列举,直观感受 LIS

先列出原序列的部分非递减子序列,再找出最长的:

  • 长度 1:ACEBF(单个元素都是非递减子序列);
  • 长度 2:A,CA,EA,BA,FC,EC,FE,FB,F
  • 长度 3:A,C,EA,C,FA,E,FC,E,FA,B,F
  • 长度 4:A,C,E,F(满足非递减:A≤C≤E≤F)、A,C,B,F(不满足,C>B)、A,E,B,F(不满足,E>B);
  • 长度 5:无(原序列 A,C,E,B,F 中 E>B,不满足非递减)。

结论:该序列的 LIS 长度为 4,对应的 LIS 可以是 A,C,E,F(还有其他可能吗?比如 A,C,E,F 是唯一长度 4 的,因为其他组合都会出现 “前大后小” 的断层)。

步骤 2:高效计算 LIS(贪心 + 二分查找)

暴力列举仅适用于短序列,对于 n=1e6 的大规模数据,需用 “贪心 + 二分查找” 算法(O (n log n) 复杂度)。我们用 ACEBF 演示该算法的核心 —— 维护一个「最小结尾元素数组 tails」:

tails 数组的含义:tails[k] 表示「长度为 k+1 的非递减子序列的最小可能结尾元素」。目的是让子序列的结尾尽可能小,为后续元素留出更多 “延长” 的空间。

算法执行过程(原序列:A(0) → C(2) → E(4) → B(1) → F(5)
  1. 处理第一个元素 A(0)

    • tails 为空,直接加入 A
    • tails = [A](此时长度 1 的子序列最小结尾是 A)。
  2. 处理第二个元素 C(2)

    • C ≥ tails[-1]C≥A),直接追加。
    • tails = [A, C](长度 2 的子序列最小结尾是 C)。
  3. 处理第三个元素 E(4)

    • E ≥ tails[-1]E≥C),直接追加。
    • tails = [A, C, E](长度 3 的子序列最小结尾是 E)。
  4. 处理第四个元素 B(1)

    • B < tails[-1]B<E),需在 tails 中找第一个大于 B 的元素,用 B 替换它。
    • 二分查找 tails [A,C,E]:第一个大于 B 的是 C,替换为 B
    • tails = [A, B, E](此时长度 2 的子序列最小结尾从 C 变为 B,后续元素更易延长)。
  5. 处理第五个元素 F(5)

    • F ≥ tails[-1]F≥E),直接追加。
    • tails = [A, B, E, F]
最终结果

tails 数组的长度为 4,即 LIS 长度 = 4(与暴力列举一致)。

三、LIS 与 “最小修改代价” 的关联

回到原问题:修改序列为非递减的最小代价 = 序列总长度 - LIS 长度。以 ACEBF 为例:

  • 序列总长度 n=5
  • LIS 长度 = 4(无需修改的最长部分);
  • 最小代价 = 5 - 4 = 1(只需修改 1 个元素,如将 B 改为 E,得到 ACEEF)。

本质:LIS 是原序列中 “天然符合非递减要求” 的最长片段,保留这部分,修改其余元素即可,修改次数自然最少。

四、拓展例子:LIS 不唯一的情况

原序列:A B C B A F(数字:0 1 2 1 0 5

  • 可能的 LIS:A,B,C,F(长度 4)、A,B,B,F(长度 4)、A,B,A,F(不满足,B>A)。
  • LIS 长度固定为 4,最小代价 = 6 - 4 = 2。

总结

  1. LIS 是 “非递减 + 子序列 + 最长” 的结合体,长度是核心指标;
  2. 短序列可暴力列举,长序列需用 “贪心 + 二分查找” 维护 tails 数组高效计算;
  3. 与原问题直接关联:LIS 长度决定了 “无需修改的最大部分”,从而推导出最小修改代价。

之所以是 “ACEEF”,是因为:

  1. 先找到了最长的 “无需修改” 的子序列 A→C→E→F
  2. 定位到唯一需要修改的元素是 “B”;
  3. 把 “B” 改成符合前后非递减关系的 “E”,就得到了合法序列,且代价最小(仅 1 次修改)。

python 代码实现

为什么用 bisect_right 而非手动二分?

bisect_right 是用 C 语言实现的内置函数,执行效率远高于 Python 手动编写的二分查找循环(尤其是在 n=1e6 这样的大规模场景中),这也是解决超时问题的关键优化之一

def solution():n = int(input())s = input()tails = []  # 存储LISfor ch in s:left = 0right = len(tails)  # tail 的当前长度while left < right:mid = left + (right - left) // 2if tails[mid] <= ch:left = mid + 1else:right = midif len(tails) == left:tails.append(ch)else:tails[left] = ch# print(tails)# print(f"最小代价{n-len(tails)}")print(n - len(tails))solution()   运行超时

import sysdef solution():# 用sys.stdin读取输入,处理大规模数据更高效data = sys.stdin.read().split()n = int(data[0])s = data[1]tails = []for ch in s:left = 0right = len(tails)# 二分查找保持不变(核心逻辑正确)while left < right:mid = left + (right - left) // 2if tails[mid] <= ch:left = mid + 1else:right = midif left == len(tails):tails.append(ch)else:tails[left] = chprint(n - len(tails))solution()

‘使用内置函数避免超时

import bisectn = int(input())
s = input().strip()tails = []  # 存储LIS的最小结尾元素for c in s:# 找到tails中第一个大于c的位置(bisect_right返回插入点)idx = bisect.bisect_right(tails, c)if idx == len(tails):# 当前字符可延长LIS,直接追加tails.append(c)else:# 替换为更小的结尾元素,为后续字符留空间tails[idx] = c# 最小代价 = 总长度 - LIS长度
print(n - len(tails))

C++ 

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;#define N 1000000
char arr[N + 5] = {0};int main()
{int n;cin >> n;string str;cin >> str;arr[0] = '1';for(int i = 0; i < n; i++){char cur = str[i];int len = strlen(arr) - 1;//cout << "i : " << i << " len : " << len << endl;
//         if(len == 0) 
//         {
//             arr[1] = cur;
//             continue;
//         }//std::cout << "arr: " << arr;int left = 0, right = len;while(left < right){int mid = (right - left) / 2 + left + 1;if(arr[mid] <= cur){left = mid;}else{right = mid - 1;}}//std::cout << "left: " << left << "right: " << right << endl;arr[right + 1] = cur;len = strlen(arr);// for(int i = 0; i < len; i++) cout << arr[i] << " ";// cout << endl;}int len = strlen(arr) - 1;cout << n - len;return 0;
}

代码解析

  1. 核心逻辑:利用 bisect.bisect_right 实现二分查找,高效维护 tails 数组(存储 LIS 的最小结尾元素),最终通过 n - len(tails) 得到最小修改代价。

  2. 二分查找的作用

    • bisect.bisect_right(tails, c) 返回 c 在 tails 中第一个大于 c 的位置,确保找到替换或追加的正确位置。
    • 例如处理 ACEBF 中的 B 时,tails 此时为 ['A','C','E']bisect_right 找到 C(索引 1)是第一个大于 B 的元素,将其替换为 Btails 变为 ['A','B','E']
  3. 时间复杂度:遍历字符串的时间为 O(n),每次二分查找的时间为 O(log k)k 为当前 tails 长度,最大为 n),整体复杂度为 O(n log n),可处理 n=1e6 的大规模输入。

  4. 示例验证:对于输入 ACEBFtails 最终会变为 ['A','B','E','F'](长度 4),因此最小代价为 5-4=1,与预期结果一致。

http://www.dtcms.com/a/392887.html

相关文章:

  • vmware安装uos v20无法识别网卡
  • 力扣hot100 | 动态规划1 | 70. 爬楼梯、118. 杨辉三角、198. 打家劫舍、279. 完全平方数、322. 零钱兑换
  • 每天五分钟深度学习:softmax回归的交叉熵损失的前向传播
  • leetcode算法刷题的第四十天
  • 算法基础篇(3)高精度
  • Java Log
  • 最常见的MCP服务
  • 如何安装tomcat服务器以及如何解决服务器的乱码问题
  • 软考中级习题与解答——第九章_信息安全(1)
  • 小迪安全v2023学习笔记(八十五讲)—— APP攻防反证书反代理反模拟器绕过XP框架
  • Oracle VM 设置CentOS7网络
  • lua代码解析1
  • C++特性之构造函数,析构函数和虚析构函数
  • 走进Linux的世界:gdb的使用
  • SCADE One vs Scade 6 - CNN池化层建模对比
  • uniapp | u-waterfall实现瀑布流商品列表(支持筛选查询)
  • C++优选算法精选100道编程题(附有图解和源码)
  • 五分钟系列-nm工具
  • 【龙泽科技】新能源汽车空调系统结构原理仿真教学软件
  • 设计一个图片上传服务,支持每秒5000张图片上传,并且要实时生成多种尺寸的缩略图。你觉得架构设计的要点有哪些?
  • NLP:Transformer优势详解
  • 基于SpringBoot+Vue的民宿管理系统(WebSocket及时通讯、腾讯地图API、支付宝沙盒支付、ECharts图形化分析)
  • Git版本管理工具入门及常用命令讲解---小白版
  • 芯脉:面向高速接口的SoC架构与完整性设计<2-2>
  • Go基础:Go语言流程控制详解
  • 【硬件-笔试面试题-103】硬件/电子工程师,笔试面试题(知识点:项目当中无人机的控制是怎么实现的)
  • 融智学的信息科学与智能科学(信智科学)基础研究
  • PyTorch 容器类详解:nn.Sequential、nn.ModuleList 与 nn.ModuleDict
  • 基于规则的专家系统对自然语言处理深层语义分析的影响与启示综合研究报告
  • 微服务配置管理