一、问题

有一个长为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)的更多相关文章

  1. 动态规划之一最长上升子序列LIS

    //最长上升子序列 #include<iostream> #include<cstring> using namespace std; const int maxn = 101 ...

  2. 动态规划(DP),最长递增子序列(LIS)

    题目链接:http://poj.org/problem?id=2533 解题报告: 状态转移方程: dp[i]表示以a[i]为结尾的LIS长度 状态转移方程: dp[0]=1; dp[i]=max(d ...

  3. 2.16 最长递增子序列 LIS

    [本文链接] http://www.cnblogs.com/hellogiser/p/dp-of-LIS.html [分析] 思路一:设序列为A,对序列进行排序后得到B,那么A的最长递增子序列LIS就 ...

  4. 最长回文子序列LCS,最长递增子序列LIS及相互联系

    最长公共子序列LCS Lintcode 77. 最长公共子序列 LCS问题是求两个字符串的最长公共子序列 \[ dp[i][j] = \left\{\begin{matrix} & max(d ...

  5. 最长上升子序列LIS(51nod1134)

    1134 最长递增子序列 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 给出长度为N的数组,找出这个数组的最长递增子序列.(递增子序列是指,子序列的元素是递 ...

  6. 【部分转载】:【lower_bound、upperbound讲解、二分查找、最长上升子序列(LIS)、最长下降子序列模版】

    二分 lower_bound lower_bound()在一个区间内进行二分查找,返回第一个大于等于目标值的位置(地址) upper_bound upper_bound()与lower_bound() ...

  7. 题解 最长上升子序列 LIS

    最长上升子序列 LIS Description 给出一个 1 ∼ n (n ≤ 10^5) 的排列 P 求其最长上升子序列长度 Input 第一行一个正整数n,表示序列中整数个数: 第二行是空格隔开的 ...

  8. 一个数组求其最长递增子序列(LIS)

    一个数组求其最长递增子序列(LIS) 例如数组{3, 1, 4, 2, 3, 9, 4, 6}的LIS是{1, 2, 3, 4, 6},长度为5,假设数组长度为N,求数组的LIS的长度, 需要一个额外 ...

  9. 1. 线性DP 300. 最长上升子序列 (LIS)

    最经典单串: 300. 最长上升子序列 (LIS) https://leetcode-cn.com/problems/longest-increasing-subsequence/submission ...

随机推荐

  1. 20个Flutter实例视频教程-第06节: 酷炫的路由动画-2

    博客地址: https://jspang.com/post/flutterDemo.html#toc-94f 视频地址: https://jspang.com/post/flutterDemo.htm ...

  2. Flutter实战视频-移动电商-30.列表页_商品列表UI界面布局

    30.列表页_商品列表UI界面布局 小程序里面的布局方式 小程序的图片上这里使用的是warp布局,因为首页里面火爆专区,已经用过了warp来布局了. 所以这里我们没有必要再讲一遍,这里我们使用List ...

  3. 01.课程介绍 & 02.最小可行化产品MVP

    01.课程介绍 02.最小可行化产品MVP 产品开发过程 最小化和可用之间找到一个平衡点

  4. UVaLive 3695 Distant Galaxy (扫描线)

    题意:给平面上的 n 个点,找出一个矩形,使得边界上包含尽量多的点. 析:如果暴力那么就是枚举上下边界,左右边界,还得统计个数,时间复杂度太高,所以我们考虑用扫描线来做,枚举上下边界, 然后用其他方法 ...

  5. forEach方法如何跳出循环

    1.for方法跳出循环 function getItemById(arr, id) { var item = null; for (var i = 0; i < arr.length; i++) ...

  6. PV(Pageviews)、访问(Visits)和访问者(Visitors)的区别

    1. 在GA上,每个页面每次加载将被记为一次PV.举例来说,一次用户访问页面顺序为:页面A->页面B->页面A,然后离开了你的站点,那这次用户访问(Visits)的PV总计为3次.   2 ...

  7. CSS优先级、CSS选择器、编写CSS时的注意事项

    CSS的优先级: 内嵌样式>ID选择器>类选择器>标签选择器 内部样式>内部样式>外部样式 CSS的选择器: 选择器:在 CSS 中,选择器是一种模式,用于选择需要添加样 ...

  8. luoguP4242树上的毒瘤

    传送门 模板集合吧,除了码农,没啥难的... 和bzoj2243:[SDOI2011]染色十分相像,但是多了点集和查询的区别 然后点集显然可以看出是虚树问题,查询可以用点分治\(O(nlogn)\), ...

  9. route(2018.10.24)

    建出最短路图之后\(topsort\)即可. 具体思路: 先用\(dijkstra\)算法在原图中跑出\(1\)号点到\(i\)号节点的最短距离\(dist_1(i)\),将所有边反向后用\(dijk ...

  10. Syncd-开源自动化部署工具

    官网地址:https://gitee.com/dreamans/syncd/issues syncd是一款开源的代码部署工具,它具有简单.高效.易用等特点,可以提高团队的工作效率. 目前只支持类Lin ...