首页 > 编程语言 >[算法学习笔记] O(nlogn)求最长上升子序列

[算法学习笔记] O(nlogn)求最长上升子序列

时间:2023-08-22 19:58:21浏览次数:46  
标签:include int mid len 算法 笔记 序列 nlogn 最长

朴素 dp 求最长上升子序列

大家应该都会朴素 dp 求最长上升子序列,简单回忆一下。

我们令 \(f_i\) 表示以 第 \(i\) 位元素为结尾的最长上升子序列长度。满足 \(\forall j < i\),则有:

\(f_i = max(f_i,f_j+1)[a_j < a_i]\)

Explanation : \(a_i\) 前面若有多个可以拼接的序列,则拼一个长度最大的。

实现方式如下:

朴素 dp 求最长上升子序列代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 100010
using namespace std;
int n;
int a[N];
int f[N];
int maxn = -1;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i] = 1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++) 
		{
			if(a[j] < a[i]) f[i] = max(f[i],f[j]+1);
		}
		maxn = max(maxn,f[i]);
	}
	cout<<maxn<<endl;
	return 0;
 } 

O(nlogn) 二分优化

上述方式求最长上升子序列的时间复杂度是 \(O(n^2)\) 的。我们考虑如何优化。

不妨令 \(f_i\) 表示最长上升子序列的长度为 \(i\) 的时候序列末尾最小的元素。这里渗透了贪心的思想,我们一定希望末尾元素最小,以便更好的拼接后面的元素。

考虑转移,设 \(len\) 表示当前的最长上升子序列长度。

  • 对于 \(a_i > f_{len}\),我们先拼接,因为在前 \(i\) 位中这就是最优解。也就是 \(f_{++len}=a_i\)

  • 对于 \(a_i \leq f_{len}\),我们显然需要在 \(f\) 中找到一个可以拼上它的地方。由于是最长上升子序列,\(f\) 数组中的内容一定是 单调递增的,因此可以二分查找。这也是把 \(O(n^2)\) 算法优化成 \(O(nlogn)\) 算法的关键。

具体地,若 \(a_i < f_l\),则 \(f_l=min(f_l,a_i)\)

最后输出 \(len\) 即可。时间复杂度 \(O(nlogn)\)

实现代码
    for(int i=1;i<=n;i++)
    {
        int l = 0,r = len;
        if(b[i] > f[len]) f[++len] = b[i];
        else
        {
            int mid;
            while(l < r) //二分查找
            {
                mid = (l+r) / 2;
                if(f[mid] > b[i]) r = mid;
                else l = mid + 1;
            }
            f[l] = min(f[l],b[i]);
        }
    }

典例:Luogu P1439 【模板】最长公共子序列

Problem

首先介绍一下朴素 \(O(n^2)\) 求最长公共子序列,然后再讲解本题。

设计状态: \(f_{i,j}\) 表示 \(a\) 数组前 \(i\) 位,\(b\) 数组前 \(j\) 位的最长公共子序列长度。

转移:\(n^2\) 枚举 \(a,b\)。对于 \(a_i,b_j\):

  • \(a_i = b_j\) ,考虑拼接。即 \(f_{i,j}=f_{i-1,j-1}+1\)。

  • \(a_i \ne b_j\),无法拼接,考虑继承。即 \(f_{i,j}=max(f_{i-1,j},f_{i,j-1})\)。

实现代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 10001
using namespace std;
int a[N],b[N];
int 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++)
    {
        for(int j=1;j<=n;j++) 
        {
            if(a[i] ==b[j]) f[i][j] = f[i-1][j-1] + 1;
            else f[i][j] = max(f[i-1][j],f[i][j-1]);
        }
    }
    cout<<f[n][n]<<endl;
    return 0;
}

回到本题。

对于 \(100\%\) 的数据, \(n \le 10^5\)。

朴素 \(n^2\) 求最长公共子序列显然无法接受。

如何处理呢?本题保证了输入的数据是排列,即内容为 \(1,2,3...n\)(不保证顺序)的数组。
这样显然数组 \(a,b\) 的内容是完全相同的,只是位置不一定相同。

不妨设 \(num_i\) 表示 \(a_i\) 在 \(b_i\) 的编号。

显然 \(b\) 的编号是严格递增的,若 \(num\) 的一部分严格递增,则这部分可以纳入 LCS(也就是说这部分属于公共子序列)。这样我们就只需要求 \(num\) 数组的最长公共子序列即可。

实现代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1000010;
const int INF = 0x3f3f3f3f;
int num[N];
int a[N],b[N];
int f[N];
int n;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],num[a[i]] = i;
    for(int i=1;i<=n;i++) cin>>b[i],f[i] = INF;
    int len = 0;
    f[0] = 0;
    for(int i=1;i<=n;i++)
    {
        int l = 0,r = len;
        if(num[b[i]] > f[len]) f[++len] = num[b[i]];
        else
        {
            int mid;
            while(l < r)
            {
                mid = (l+r) / 2;
                if(f[mid] > num[b[i]]) r = mid;
                else l = mid + 1;
            }
            f[l] = min(f[l],num[b[i]]);
        }
    }
    cout<<len<<endl;
    return 0;
}

标签:include,int,mid,len,算法,笔记,序列,nlogn,最长
From: https://www.cnblogs.com/SXqwq/p/17649540.html

相关文章

  • openGauss学习笔记-47 openGauss 高级数据管理-权限
    openGauss学习笔记-47openGauss高级数据管理-权限数据库对象创建后,进行对象创建的用户就是该对象的所有者。数据库安装后的默认情况下,未开启三权分立,数据库系统管理员具有与对象所有者相同的权限。也就是说对象创建后,默认只有对象所有者或者系统管理员可以查询、修改和销毁对象,......
  • 【笔记】机器学习基础 - Ch5. Support Vector Machines
    5.1Linearclassification考虑如下问题:\(\mathbb{R}^N\)上的\(\calX\)服从某个未知分布\(\calD\),并由目标函数\(f:\calX\toY\)映射到\(\{-1,+1\}\)。根据采样\(S=(({\bfx}_1,y_1),\dotsb,({\bfx}_m,y_m))\)确定一个二分类器\(h\in\calH\),使得其泛化......
  • JavaSE学习笔记
    Java基础数据类型扩展及面试题讲解整数拓展: 进制、二进制0b、十进制、八进制0、十六进制0x浮点数拓展:银行业务怎么表示?钱——最好完全避免使用浮点数进行比较使用BigDecimal数学工具类float:有限、离散、舍入误差、大约、接近但不等于double:精度问题字符拓......
  • 年薪40W,如何高效准备大厂AI算法岗面试?
    如果说求职是人生的一道坎,那么面试就是最难翻越的那一块砖。当你经历过大大小小的面试之后,就会发现不同的公司、不同的面试官问的问题都大同小异,因为企业对于挑选人才是有一些共性的要求的,只要在面试前根据高频问题提前准备,命中率几乎等于100%!但是,为什么还有很多技术精湛、经验丰富......
  • 『复习笔记』树链剖分(重链剖分)
    前言(事出必有因)今天模拟赛有一道线段树+LCA的题,考场上码了两个小时,结果pushup错了,结果线段树调完了,发现TLE了,原来是求LCA炸了。。因为我用的倍增(我就是倍增狗你能把我怎么样),但是倍增的一个重要问题就是log都跑满了,但是树剖跑不满log,别问我为什么不用Tarjan,因为这题是在线的不好......
  • cwltoo学习笔记
    执行工作流:cwltool/home/zcy/download/cwl/wf.cwl/home/zcy/download/cwl/echo-job.ymlwf.cwlcwlVersion:v1.2class:Workflowinputs:message:stringoutputs:out:type:FileoutputSource:uppercase/example_outsteps:echo:run:/home/......
  • VNPY-网络交易(算法交易)
    fromvnpy.trader.constantimportDirectionfromvnpy.trader.objectimportTradeData,OrderData,TickDatafromvnpy.trader.engineimportBaseEnginefromvnpy.trader.constantimportOrderType,Offset,Directionfrom..templateimportAlgoTemplateimportmat......
  • Vue学习笔记:Pinia Part01
    介绍Pinia是Vue的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式API的话,你可能会认为可以通过一行简单的 exportconststate=reactive({}) 来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞......
  • javascript学习笔记第五天
    今天的笔记functiongetusergradesum(arr=[])传递数组进入匿名函数,假设不确定数组是否会为空可以默认传一个空的数组进入,这样不会报错在匿名方法里面,return之后就直接结束函数了三元运算符好像不能同时使用两个return,例如i>l?returni:retuenl,这样子会报错return时......
  • [代码随想录]Day24-回溯算法part04
    题目:93.复原IP地址思路:函数参数:参数就一个stirng,path先收集ip地址的四个部分,最后存入res中时拼接成一个string,因此path和res都是[]string类型终止条件:当path有了ip的四个部分就终止;当然只有完全的把字符串遍历了才会存入res单层逻辑:先取1-3位,判断是不是0-255内的数并且没......