动态规划初步--最长上升子序列(LIS)
一、问题
有一个长为n的数列 a0,a1,a2...,an-1a。请求出这个序列中最长的上升子序列的长度和对应的子序列。上升子序列指的是对任意的i < j都满足ai < aj的子序列。
二、思路
如果i < j且ai < aj则认为ai到aj存在有向边,由于一个数不可能直接或间接的指向自己,所以是一个有向无环图。但是,在这里我们并不需要真正的建立图。我们可以用动态规划来做,对于状态的设定有多种方式。
三、代码实现
1、将dp[i]表示以ai结束的最长上升子序列,当j < i且a[j] < a[i],dp[i] = max(dp[i],dp[j] + 1),初始化dp[i] = 1。由于定义的是结束位置,所以记录的是逆序,用栈倒过来再输出就行。
代码一:
#include<stdio.h>
#include<iostream>
#include<stack>
#include<algorithm>
using namespace std; const int INF = 0x3f3f3f3f;
const int maxn = + ;
int n;
int a[maxn], dp[maxn]; //dp[i]表示以i结束的最长上升子序列
int nextp[maxn]; //记录路径
int res = ;
int first = ; void print_ans()
{
stack<int>s;
for (int i = ; i < res; i++)
{
s.push(first + );
first = nextp[first];
}
for (int i = ; i < res; i++)
{
int tmp = s.top(); s.pop();
printf("%d ", tmp);
}
printf("\n%d\n", res);
} void slove()
{
memset(dp, , sizeof(dp));
memset(nextp, , sizeof(nextp));
res = ; first = ; //记得清零
for (int i = ; i < n; i++)
{
dp[i] = ;
for (int j = ; j < i; j++)
{
if (a[j] < a[i])
{
if (dp[j] + > dp[i])
{
dp[i] = dp[j] + ;
nextp[i] = j;
}
}
}
if (dp[i] > res)
{
res = dp[i];
first = i;
}
}
print_ans();
} int main()
{
while (scanf("%d", &n) == && n)
{
for (int i = ; i < n; i++)
scanf("%d", &a[i]);
slove();
}
return ;
}
代码二:两者只是输出的处理不同,用逆向寻路,不需要额外的空间,但要消耗一些时间。
#include<stdio.h>
#include<iostream>
#include<stack>
#include<vector>
#include<algorithm>
using namespace std; const int INF = 0x3f3f3f3f;
const int maxn = + ;
int n;
int a[maxn], dp[maxn]; //dp[i]表示以i结束的最长上升子序列
stack<int>sta; void print_ans(int s)
{
sta.push(s + );
if (dp[s] == )
{
while (!sta.empty())
{
int tmp = sta.top(); sta.pop();
printf("%d ", tmp);
}
return;
}
for (int i = ; i < n; i++)
{
if (a[i] < a[s] && dp[s] == dp[i] + )
{
print_ans(i);
break;
}
}
}
void slove()
{
memset(dp, , sizeof(dp));
int res = ;
int first = ;
stack<int>s;
for (int i = ; i < n; i++)
{
dp[i] = ;
for (int j = ; j < i; j++)
{
if (a[j] < a[i])
{
dp[i] = max(dp[i],dp[j] + );
}
}
if (dp[i] > res)
{
res = dp[i];
first = i; //first记录最长上升子序列的最后元素的位置
}
} print_ans(first);
printf("\n%d\n", res);
} int main()
{
while (scanf("%d", &n) == && n)
{
for (int i = ; i < n; i++)
scanf("%d", &a[i]);
slove();
}
return ;
}
2、用dp[i]表示以i开始的最长上升子序列,初始化同样是dp[i] = 0。同时由于定义的是开始位置,可以直接输出最长上升子序列。
void print_ans()
{
for (int i = ; i < res; i++)
{
printf("%d ", first + );
first = nextp[first];
}
printf("\n%d\n", res);
} void slove()
{
res = ; //定义成全局变量时,注意清零
for (int i = n - ;i >= ; i--)
{
dp[i] = ;
for (int j = i + ; j < n; j++)
{
if (a[i] < a[j])
{
if (dp[j] + > dp[i])
{
dp[i] = dp[j] + ;
nextp[i] = j;
}
}
}
if (dp[i] > res)
{
res = dp[i];
first = i;
}
}
print_ans();
}
3、把dp[i]表示长度为i + 1的上升子序列中末尾元素的最小值(不存在的话就是INF)。
贪心 + 二分查找,利用贪心思想,对于一个一个上升子序列,显然最后一个元素越小,越有利于添加新的元素,这样序列就越长。
类似的我们也可以定义对称的状态,即把dp[i]表示长度为i + 1的上升子序列中开始元素的最大值
相比前面的状态定义,有两个好处:如果子序列长度相同,可以使最末尾元素的值最小;时间复杂度由O(n^2)降至O(nlogn)。
开始全部初始化为INF,如果i == 0或者dp[i - 1] < aj,dp[i] = min(dp[i],aj),最终dp[i] < INF,最大的i + 1就是结果。如何找到aj呢,由于dp[i]记录的长度为i + 1的最末尾元素的最小值,而最末尾元素是这个长度为i+1中的最大值,所以aj大于dp[i]就能更新。我们还能发现,dp单增,每个aj只需用来更来一次。

n个元素,每次查找logn,总的时间复杂度为O(nlogn).
void slove()
{
fill(dp, dp + n, INF);
for (int i = ; i < n; i++)
{
*upper_bound(dp, dp + n, a[i]) = a[i];
}
int first = lower_bound(dp, dp + n, INF) - dp;
printf("%d\n", dp[first - ]);
printf("%d\n", first);
}
打印结果路径似乎不太方便,以后再补上吧。
动态规划初步--最长上升子序列(LIS)的更多相关文章
- 动态规划之一最长上升子序列LIS
//最长上升子序列 #include<iostream> #include<cstring> using namespace std; const int maxn = 101 ...
- 动态规划(DP),最长递增子序列(LIS)
题目链接:http://poj.org/problem?id=2533 解题报告: 状态转移方程: dp[i]表示以a[i]为结尾的LIS长度 状态转移方程: dp[0]=1; dp[i]=max(d ...
- 2.16 最长递增子序列 LIS
[本文链接] http://www.cnblogs.com/hellogiser/p/dp-of-LIS.html [分析] 思路一:设序列为A,对序列进行排序后得到B,那么A的最长递增子序列LIS就 ...
- 最长回文子序列LCS,最长递增子序列LIS及相互联系
最长公共子序列LCS Lintcode 77. 最长公共子序列 LCS问题是求两个字符串的最长公共子序列 \[ dp[i][j] = \left\{\begin{matrix} & max(d ...
- 最长上升子序列LIS(51nod1134)
1134 最长递增子序列 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 给出长度为N的数组,找出这个数组的最长递增子序列.(递增子序列是指,子序列的元素是递 ...
- 【部分转载】:【lower_bound、upperbound讲解、二分查找、最长上升子序列(LIS)、最长下降子序列模版】
二分 lower_bound lower_bound()在一个区间内进行二分查找,返回第一个大于等于目标值的位置(地址) upper_bound upper_bound()与lower_bound() ...
- 题解 最长上升子序列 LIS
最长上升子序列 LIS Description 给出一个 1 ∼ n (n ≤ 10^5) 的排列 P 求其最长上升子序列长度 Input 第一行一个正整数n,表示序列中整数个数: 第二行是空格隔开的 ...
- 一个数组求其最长递增子序列(LIS)
一个数组求其最长递增子序列(LIS) 例如数组{3, 1, 4, 2, 3, 9, 4, 6}的LIS是{1, 2, 3, 4, 6},长度为5,假设数组长度为N,求数组的LIS的长度, 需要一个额外 ...
- 1. 线性DP 300. 最长上升子序列 (LIS)
最经典单串: 300. 最长上升子序列 (LIS) https://leetcode-cn.com/problems/longest-increasing-subsequence/submission ...
随机推荐
- PHP实用小程序(四)
<HTML> <HEAD> <TITLE>访问文件时间属性</TITLE> </HEAD> <BODY> <? $Last ...
- 洛谷 - P1390 - 公约数的和 - 莫比乌斯反演 - 欧拉函数
https://www.luogu.org/problemnew/show/P1390 求 $\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m} gcd(i,j) $ ...
- ajax 的三种使用方法
第一种 也是最古老的一种方法之一 from 表单直接提交数据到php文件里 action为路径 <form method="post" action="./inde ...
- shell chpasswd 命令 修改用户密码
使用useradd 命令增加一个用户后,它默认是没有设置密码的.如果需要给用户设置或者修改密码,一般会使用passwd命名. 但是passwd命令有一个缺陷,它需要人工交互操作. 如果你是一名系统管理 ...
- 5本自然语言处理书单-附pdf
文章发布于公号[数智物语] (ID:decision_engine),关注公号不错过每一篇干货. 自然语言处理(英语:Natural Language Processing,缩写作 NLP)是人工智能 ...
- STP-1-802.1D生成树协议及改进
第一个 IEEE 标准化的STP也常称为“ 传统”STP,最初在802. 1D中进行了描述. 之后得到了改进,发布在所谓的修正案中:快速 STP( RSTP),在修正案802. 1w 中描述了它的标准 ...
- Debug和Release版本区别
Debug和Release版本区别 众所周知,我们尽心iOS开发,在Xocde调试程序时,分为两种方式Debug和Release,在Target的Setting中相信大家应该能看到很多选项分别为Deb ...
- traceback异常打印
traceback模块 traceback模块被用来跟踪异常返回信息. 如下例所示: import traceback try: raise SyntaxError, "traceback ...
- POJ-3159-Candies-(差分约束,Dijkstra)
链接:https://vjudge.net/problem/POJ-3159 题意: N个小孩,M个约束 以A,B,C给出.即小孩B的糖果不能比A多C以上. 思路: 差分约束: 若有 A-B < ...
- python库使用整理
1. 环境搭建 l Python安装包:www.python.org l Microsoft Visual C++ Compiler for Python l pip(get-pip.py):p ...