首先区分子序列和子串的区别,子串必须是连续的,子序列可以是不连续的
例题 https://www.luogu.com.cn/problem/P1020
算法一:贪心加二分实现(时间复杂度 : n*log(n))
函数:
lower_bound(ForwardIter first, ForwardIter last,const _Tp& val)算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置。
upper_bound(ForwardIter first, ForwardIter last, const _Tp& val)算法返回一个非递减序列[first, last)中的第一个大于值val的位置。
函数内部实现是二分。
简单思路:建一个数组a来存储当前长度为i时最长上升子序列的最小的尾值,因为对于LIS来说他的尾值越小越有利于其变的更长。当当前数大于数组a的尾值时,直接加入,当当前数小于尾值时,从数组中找出第一个大于等于当前数的值,将其替换。
代码:
int len=0;
for(int i=1;i<n;i++)
{
int pos=lower_bound(f, f+len, a[i]) - f;
len=max(len,pos+1);
f[pos]=a[i];
}
cout <<len<<endl;
//上升
int len=0;
for(int i=1;i<n;i++)
{
int pos=upper_bound(f, f+len, a[i], greater<int>()) - f;
//更新序列结尾第一个小于a[i]的序列
len=max(len,pos+1);
f[pos]=a[i];
}
cout <<len<<endl;
//下降
greater<int>() c++中的大于函数
算法二: DP+树状数组优化
简单思路:设数组dp[i]表示 长度为i以a[i]结尾的序列的最长上升子序列长度
状态转移方程 : dp[i]=max(dp[i],dp[j]+1);
但是对于朴素的DP,需要每次都枚举前i个数去找,时间复杂度达到(n*n)需要用树状数组来优化每次找比他小的数的位置这一过程。
struct Node{
int val,num;
}z[maxn];
bool cmp(Node a,Node b)
{
return a.val==b.val?a.num<b.num:a.val<b.val;
}
void modify(int x, int y)
//把val[x]替换为val[x]和y中较大的数
{
for(; x<=n; x+=x&(-x))
T[x] = max(T[x],y);
}
int query(int x)
//返回val[1]~val[x]中的最大值
{
int res=-INF;
for(; x; x-=x&(-x))
res=max(res,T[x]);
return res;
}
for(int i=1; i<=n; i++)
{
scanf("%d", &z[i].val);
z[i].num = i;
}
sort(z+1, z+n+1, cmp);
//以权值从小到大排序
for(int i=1; i<=n; i++)
//按权值从小到大枚举
{
int maxx = query(z[i].num);
//查询编号小于等于num[i]的LIS最大长度
modify(z[i].num, ++maxx);
//把长度+1,再去更新前面的LIS长度
ans=max(ans, maxx);
}