动态规划初步--最长上升子序列(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 ...
随机推荐
- 【Linux学习】Linux系统管理2—作业调度
Linux系统管理2-作业调度 at: 作业仅执行一次就从系统工作队列中取消 语法 denny@ubuntu:~$ at [-m] TIME → 作业命令at ...
- Flutter实战视频-移动电商-33.列表页_子类和商品列表交互效果
33.列表页_子类和商品列表交互效果 主要实现点击小类下面的列表跟着切换 获取右侧下面的列表信息,即要传递大类的id也要传递小类的,所以需要把左侧的大类的id也要Provide化 可以看下网站上的接口 ...
- JAVA企业级开发-session,cookie(13)
一. jsp jsp: java server pager .java动态生成网页的一门技术 使用servlet生成网页时候遇到的问题,为什么使用jsp? 在servlet里面书写htm ...
- python3 requests模块 基本操作
import requests import json # 1.HTTP方法 requests.get('https://github.com/timeline.json') #GET请求 reque ...
- [题解](堆)luogu_P1631序列合并
思路来自题解 作者: Red_w1nE 更新时间: 2016-11-13 20:46 在Ta的博客查看 72 最近有点忙 没时间贴代码了== [分析] 首先,把A和B两个序列分别从小到大排序,变成两 ...
- C++中this指针的理解
C++中this指针的理解 先要理解class的意思.class应该理解为一种类型,象int,char一样,是用户自定义的类型.用这个类型可以来声明一个变量,比如int x, myclass my等等 ...
- Serega and Fun Codeforces - 455D || queue
https://codeforces.com/problemset/problem/455/D 其实方法很多,然而当初一个也想不到... 1.分块,块内用链表维护 修改[l,r]就当成删除第r个元素, ...
- 洛谷 P1094 纪念品分组
P1094 纪念品分组 先按价格对纪念品排序(这里是从大到小),然后从两端向中心开始配对,有两个变量i和j,表示正在处理的两个纪念品编号,开始时i=1,j=n,如果a[i]+a[j]>w则第i贵 ...
- layer窗口抖动
详细学习网址 http://www.layui.com/doc/modules/layer.html#use ..................................... //使用方法 ...
- vue echarts 大小自适应
窗口大小时候 ,echarts图自适应 在创建图表的方法中直接,用resize方法 let myChart=this.$refs.myChart; let mainChart = echarts.in ...