[[TOC]]
题目
TODO
7
1 7 3 5 9 4 8
一句话算法
每个元素都尝试拼接到它前面的所有元素的后面
题目
TODO
本题是建立在序列上一个题目.
什么是序列?
根据序列 - 维基百科,自由的百科全书的定义,序列定义在集合上的一个函数
暴力想法
根据前面所学的集合的知识,设集合为
设序列
设
那么
设
于是得知子序列
- 如果
第 位为 ,表示子序列 不含有 - 如果
第 位为 ,表示子序列 含有
数学表示为
设
序列是定义在集合上的一个函数
函数是一种特殊的二元关系
显然答案是
根据上面的式子
也就是集合
显然集合
说了那么多,其实使用的算法很简单,就是[
Rbook: 01序列]
点击
//Author by [Rainboy](https://github.com/rainboylvx)
//date: 2024-06-15 15:22:19
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
int n,m;
int a[maxn];
int b[maxn]; //桶
//记录答案
int ans;
bool is_lis() {
//前一个数
int pre = -9999999;
for(int i =1;i<=n;i++) {
if( b[i] == 1) {
if( a[i] < pre)
return false;
pre = a[i];
}
}
return true;
}
void print_seq() {
for(int i = 1;i <= n ;++i ) // i: 1->n
{
if( b[i]) cout << a[i] << " ";
}
std::cout << "\n";
}
void dfs(int dep) {
if( dep > n) {
int cnt = 0;
for(int i = 1;i <= n ;++i ) // i: 1->n
{
cnt += b[i];
}
if( cnt > ans && is_lis())
{
ans = cnt;
//调试用,输出这个序列
// print_seq();
}
return;
}
for(int i = 0;i <= 1 ;++i ) // i: 0->1
{
b[dep] = i;
dfs(dep+1);
}
}
int main () {
std::cin >> n;
for(int i = 1;i <= n ;++i ) // i: 1->n
{
cin >> a[i];
}
dfs(1);
std::cout << ans << "\n";
return 0;
}
小朋友法
原问题,设序列为
把这个问题转化为
这样就转化成了求
显然
于是我们只需要写一个两重循环可以解出答案,时间为
代码
//Author by [Rainboy](https://github.com/rainboylvx)
//date: 2024-06-15 17:45:14
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
int n,m;
int a[maxn];
//f[i] 表示以第i个元素为结尾的最长lis长度
// 边界 f[1] = 1
int f[maxn];
int ans;
int main (int argc, char *argv[]) {
std::cin >> n;
for(int i = 1;i <= n ;++i ) // i: 1->n
{
cin >> a[i];
}
f[1] = 1;
//从第2个元素开始
for(int i = 2;i <= n ;++i ) // i: 2->n
{
int t = 0;
for(int j = 1;j < i ;++j ) // j: 1->i
{
if( a[j] <= a[i] && t < f[j])
t = f[j];
}
f[i] = t + 1;
}
for(int i = 1;i <= n ;++i ) // i: 1->n
{
if( ans < f[i])
ans = f[i];
}
cout << ans;
return 0;
}
时间复杂度为:
证明
上面我们通过小朋友法,得到了一个式子如下:
似乎很突兀,你可能会有两个疑问:
- 如何证明这个公式是正确的
- 如何思考可以最终得到这个公式呢?思维的过程是什么.
下面的用集合想法来证明.
设原序列为
用数字
表示一个前n个元素组成的集合是一种常用的集合表示法.
设
根据集合分类的思想,考虑是后一个元素,要么包含最后一个元素
根据最后一个元素
,含有 的子集集合 ,不含有 的子集集合
那么需要求前
设
, ,含有 的的子集的答案 ,针对 ,得到的答案
显然
如果按这种方式继续分,可以分成到最后一个式子是
可以想到,按这方式对集合进行划分,符合不重不漏的原则,且最后所有的问题都可以转成
显然
为了方法,我们用数字来表示集合,如
显然,可以用
那么
建立起了最终问题与
思考
本质上可以把这个题目看成一个竖着的单列数字金字塔,只不过从点
延伸:最少递增子序列切分。 LIS 有一个孪生问题:把序列切成最少条递增子序列,最少切几条?答案是最长下降子序列的长度,背后是 [
Rbook: 偏序与Dilworth定理]。
练习题目
基础模板与性质
- [
luogu B3637: 最长上升子序列] (洛谷 B3637) 基础 模板 - [
noi_openjudge ch0206-1759: 最长上升子序列] - [
luogu P1020: [NOIP 1999 提高组] 导弹拦截] (洛谷 P1020) 必刷经典:贪心二分优化与 Dilworth 定理 - [
noi_openjudge ch0206-4982: 踩方格]
经典建模与变式
- [
luogu P1091: [NOIP2004 提高组] 合唱队形] (洛谷 P1091) 双向 LIS(峰形序列) - [
roj 1264: 【例9.8】合唱队形] - [
luogu P2782: 友好城市] (洛谷 P2782) 航线不交叉模型(二维排序转 LIS) - [
roj 1263: 【例9.7】友好城市] - [
leetcodecn russian-doll-envelopes: 俄罗斯套娃信封问题] (LeetCode 354) 二维偏序模型(宽升序、同宽高降序)
跨主题综合优化
- [
luogu P1439: 【模板】最长公共子序列] (洛谷 P1439) 全排列 LCS 位置映射转 LIS,优化至