IS(上升子序列)
前言:
这是一篇杂题选讲+作者口胡的博客,不喜勿喷。
正文:
提示:在阅读时请留意加粗的字体是“极长”还是“最长”。
今天改题时碰到了一道关于线段树维护单调栈求极大上升子序列的题,没看出来,懵了一整个下午,感到对这个知识点不熟,所以来总结一下。
What is IS??
其实我是将LIS删了一个L造的这个词,。所谓上升子序列,就是在原序列中抽出一个子序列(不一定连续),他满足任意\(i<j\)有\(v[i]<v[j]\)。
最长上升子序列(\(LIS\)):就是\(IS\)里最长的。
极大上升子序列:就是一个不会被其他任意一个\(IS\)包含的\(IS\)。
这两个是常考点,然而我总是看不出来。
他们的性质:
主要是考虑极长上升子序列,因为最大上升子序列的性质除了最大外就和他的性质一样了。
1.满足任意\(i<j\)有\(v[i]<v[j]\)。
2.在原序列中,极长上升子序列\({p_{1},p_{2},p_{3}......p_{m}}(p_{1}<p_{2}<p_{3}<......<p_{m})\)满足:
不存在一个\(j\),使得\(j<p_{1}\)且\(v[j]<v[p_{1}]\)。
不存在一个\(j\),使得\(j>p_{m}\)且\(v[j]>v[p_{m}]\)。
不存在一个\(j\),使得\(p_{i}<j<p_{i+1}\)且\(v[p_{i}]<v[j]<v[p_{i+1}]\)
第二条尤为重要,考试时就栽在他上面了。。。。。。
如何求??
对于最长上升子序列来说:
传统做法是线性DP。
int c[N];
for(int i=1;i<=n;i++)
{
c[i]=1;
for(int j=1;j<i;j++)
if(v[j]<v[i])
c[i]=max(c[i],c[j]+1);
}
复杂度:\(O(N^{2})\)
有一种线段树优化,叫线段树维护单调栈,可以优化至\(O(Nlog^{2}_{2}N)\)。
int c[N];
#define lc id<<1
#define rc id<<1|1
class Line_tree
{
private:
int maxn[N<<2];
public:
void insert(int,int,int,int,int);
int query(int,int,int,int,int);
}t;
void Line_tree::insert(int id,int l,int r,int pos,int val)
{
if(l==r){maxn[id]=val;return;}
int mid=(l+r)>>1;
if(pos<=mid) insert(lc,l,mid,pos,val);
else insert(rc,mid+1,r,pos,val);
maxn[id]=max(maxn[lc],maxn[rc]);
}
int Line_tree::query(int id,int l,int r,int st,int en)
{
if(st<=l&&r<=en) return maxn[id];
int mid=(l+r)>>1;
int ret=-inf;
if(st<=mid) ret=max(ret,query(lc,l,mid,st,en));
if(mid<en) ret=max(ret,query(rc,mid+1,r,st,en));
return ret;
}
#undef lc
#undef rc
//在主函数里:
for(int i=1;i<=n;i++)
{
int id=0;
if(v[i]-1>=1)
id=t.query(1,1,n,1,v[i]-1);
while(id)
{
c[i]=max(c[i],c[id]+1);
if(v[id]+1>v[i]-1)
break;
id=t.query(1,1,n,v[id]+1,v[i]-1);
}
ans=max(ans,c[i]);
}
其实它主要的用途是做一些与极大上升子序列有关的统计时用的,比如方案数统计、最小权极大上升子序列等等。。。并不推荐用来单纯求序列,因为求序列的话他的复杂度不如下者。
还有一种优化,是\(O(Nlog_{2}N)\)的,他就是主要用来求最大序列及方案数了。
#include<bits/stdc++.h>
using namespace std;
namespace STD
{
#define rr register
#define inf INT_MAX
typedef long long ll;
const int N=100004;
const int mod=123456789;
const int R=100003;
int n,type;
int r[N];
#define lc id<<1
#define rc id<<1|1
struct node
{
int maxn;
ll cnt;
node(){maxn=cnt=0;}
node(int maxn_,ll cnt_){maxn=maxn_,cnt=cnt_;}
};
class Line_tree
{
private:
node a[R<<2];
void Insert(int,int,int,int,node);
node Query(int,int,int,int,int);
public:
Line_tree(){}
void insert(int pos,node val){Insert(1,1,R-1,pos,val);}
node query(int st,int en)
{
if(st>en) return node(0,0);
return Query(1,1,R-1,st,en);
}
}t;
void Line_tree::Insert(int id,int l,int r,int pos,node val)
{
if(l==r)
{
if(a[id].maxn==val.maxn)
a[id].cnt=(a[id].cnt+val.cnt)%mod;
else if(a[id].maxn<val.maxn)
a[id]=val;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) Insert(lc,l,mid,pos,val);
else Insert(rc,mid+1,r,pos,val);
a[id]=(a[lc].maxn>a[rc].maxn)?a[lc]:a[rc];
if(a[lc].maxn==a[rc].maxn)
a[id].cnt=(a[lc].cnt+a[rc].cnt)%mod;
}
node Line_tree::Query(int id,int l,int r,int st,int en)
{
if(st<=l&&r<=en) return a[id];
int mid=(l+r)>>1;
node ret=node(-inf,0),temp;
if(st<=mid)
{
temp=Query(lc,l,mid,st,en);
if(temp.maxn>ret.maxn)
ret=temp;
else if(temp.maxn==ret.maxn)
ret.cnt=(ret.cnt+temp.cnt)%mod;
}
if(mid<en)
{
temp=Query(rc,mid+1,r,st,en);
if(temp.maxn>ret.maxn)
ret=temp;
else if(temp.maxn==ret.maxn)
ret.cnt=(ret.cnt+temp.cnt)%mod;
}
return ret;
}
#undef lc
#undef rc
int read()
{
rr int x_read=0,y_read=1;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
};
using namespace STD;
int main()
{
n=read(),type=read();
for(rr int i=1;i<=n;i++)
r[i]=read();
int ans=-inf;
ll num=0;
for(rr int i=1;i<=n;i++)
{
node temp=t.query(1,r[i]-1);
temp.maxn++;
if(temp.cnt==0)
temp.cnt=1;
if(ans<temp.maxn)
{
num=temp.cnt;
ans=temp.maxn;
}
else if(ans==temp.maxn)
num=(num+temp.cnt)%mod;
t.insert(r[i],temp);
}
cout<<ans;
if(type==1)
cout<<num;
}
其实这两种方法主要的区别是:
前者通过那个枚举id的循环枚举i之前已有的极长序列,以此为算法框架,来进行一些额外的维护,他可以知道具体的方案,即对应下标,有了下标,就可以以此为框架维护线段树之外的东西了。
后者线段树维护的是长度,因此只能局限于对长度的求解,稍带着维护方案数,他无法知道具体方案即下标,局限性相对大。
另外,在统计极长子序列的方案总数时,可以直接从后往前跳单调不降序列然后累加结果即可,就像这样:
//在这之前你已经求好了方案数
int x=-inf;//随便一个极小值
for(int i=n;i>=1;i--)
{
if(v[i]>=x)
{
x=v[i];
ans+=c[i];
}
}
2021-07-29 21:30:08 星期四 现役
IS(上升子序列)的更多相关文章
- 用python实现最长公共子序列算法(找到所有最长公共子串)
软件安全的一个小实验,正好复习一下LCS的写法. 实现LCS的算法和算法导论上的方式基本一致,都是先建好两个表,一个存储在(i,j)处当前最长公共子序列长度,另一个存储在(i,j)处的回溯方向. 相对 ...
- codevs 1576 最长上升子序列的线段树优化
题目:codevs 1576 最长严格上升子序列 链接:http://codevs.cn/problem/1576/ 优化的地方是 1到i-1 中最大的 f[j]值,并且A[j]<A[i] .根 ...
- [LeetCode] Arithmetic Slices II - Subsequence 算数切片之二 - 子序列
A sequence of numbers is called arithmetic if it consists of at least three elements and if the diff ...
- [LeetCode] Is Subsequence 是子序列
Given a string s and a string t, check if s is subsequence of t. You may assume that there is only l ...
- [LeetCode] Wiggle Subsequence 摆动子序列
A sequence of numbers is called a wiggle sequence if the differences between successive numbers stri ...
- [LeetCode] Increasing Triplet Subsequence 递增的三元子序列
Given an unsorted array return whether an increasing subsequence of length 3 exists or not in the ar ...
- [LeetCode] Distinct Subsequences 不同的子序列
Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequence ...
- 动态规划之最长公共子序列(LCS)
转自:http://segmentfault.com/blog/exploring/ LCS 问题描述 定义: 一个数列 S,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则 ...
- [Data Structure] LCSs——最长公共子序列和最长公共子串
1. 什么是 LCSs? 什么是 LCSs? 好多博友看到这几个字母可能比较困惑,因为这是我自己对两个常见问题的统称,它们分别为最长公共子序列问题(Longest-Common-Subsequence ...
- 51nod1134(最长递增子序列)
题目链接: https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1134 题意: 中文题诶~ 思路: 直接暴力的话时间复杂度为 ...
随机推荐
- P2470 压缩 TJ
前言 洛谷题解 题目传送门 正解:区间/线性 dp(本篇题解介绍线性做法) 人生第一道紫题! 也是今天考试看自闭了就没做的 T4,结果没想到是紫,虽然是一道水紫呢-- 考试的 T5 是跳房子,蓝题 q ...
- Java方法03——方法的重载
方法的重载(println 就是一个典型的重载(源码)) 重载就是在一个类中,有相同的函数名称,但形参不同的函数 方法重载的规则 方法名称必须相同 参数列表必须不同(个数不同.或者类型不同.参数排列顺 ...
- Java_classpath
Java_classpath 什么是classpath? classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class. 因为Java是编译型语言,源码文件是.java,而编译后的 ...
- JAVA基础语法:常用功能符以及循环结构和分支结构(转载)
3.JAVA基础语法:常用功能符以及循环结构和分支结构 1.常用功能符 注释 ("文字"是被注释的部分) //文字 单行注释 /文字/ 多行注释 算术运算符 + - * / / 整 ...
- 在 CSS 中表示颜色的hex code方法和rgb方法
hexadecimal code(十六进制编码),简写为 hex code. 我们通常使用 decimals,也就是十进制数字,它对每一位数字使用符号0到9来表示.Hexadecimals (或 he ...
- idea 2019.3.3 系列产品破解
所有软件版本要求 必须是2019.3.3版本,可破解idea, goland, datagrid, pycharm等系列产品. 编辑vmoptions 添加破解jar包 然后重新打开,输入激活码进行激 ...
- sqli-labs lesson1-4
写在前面: 前四关基本都是基于GET的SQL注入 在Mysql中有一个系统数据库information_schema,存储着所有数据库的相关信息,一般利用这个数据库进行SQL注入. 因为大部分的注入需 ...
- 【LeetCode】26. 删除有序数组中的重复项
26. 删除有序数组中的重复项 知识点:数组:排序:双指针: 题目描述 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度. 不要使用额外的 ...
- 题解 Omeed
传送门 差了一点没想到正解-- 首先单次询问的 \(O(n)\) 写法很好想,考虑如何优化 首先基础分区间求和即可 然后那个连击分的话,是一个关于 \(f_i\) 和 \(f_{i-1}\) 的柿子 ...
- Docker开启TLS和CA认证
前言:Docker直接开启2375端口是不安全的,别人只要连上之后就可以任意操作,下面是开启Docker的TLS和CA认证方法,并使用Jenkins和Portainer连接. 一.生成证书 查看服务器 ...