LIS问题是动态规划的入门题目,具体问题如最长上升子序列
设有由n(1≤n≤200)个整数组成的数列,记为:b1,b2……,bn,
若存在 i1<i2<……<ie,且有b1<b2<……<be则称为长度为 e 的上升子序列。
程序要求,当原数列出之后,求出最长的上升子序列。
例如 13,7,9,16,38,24,37,18,44,19,21,22,63,15。
例中 13,16,18,19,21,22,63 就是一个长度为 7 的上升子序列,
同时也有 7 ,9,16,18,19,21,22,63 组成的长度为 8 的上升子序列。
由于n的数据范围较小,所以可以直接用贪心解决这题,但如果将数据升级为1≤n≤104,你就会得到一个TLE。这时候,我们可以用记忆化搜索,也可以用动态规划,当然本质其实差距不大,都需要一个dp数组,但是我们这里主讲动态规划
思路及代码:
假设dpi表示以bi结尾的最长上升子序列的长度,根据样例可列表格
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
bi | 13 | 7 | 9 | 16 | 38 | 24 | 37 | 18 | 44 | 19 | 21 | 22 | 63 | 15 |
dpi | 1 | 1 | 2 | 3 | 4 | 4 | 5 | 4 | 6 | 5 | 6 | 7 | 8 | 3 |
可以发现,dpi=1+(maxi-1j=0dpj,bj<bi)
代码如下
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
int dp[N],n=1,b[N];
int main(){
while(cin>>b[n]&&b[n]!=0)//输入
n++;
n--;
int ans=0;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++)
if(b[j]<b[i])
dp[i]=max(dp[i],dp[j]+1);//选出前面最长的子序列
ans=max(ans,dp[i]);
}
cout<<ans;
return 0;
}
优化
前面已经讲过了O(n2)的做法,但是如果将数据升级到1≤n≤105怎么办?
这时就需要用到费脑细胞的优化方法,二分优化
假设fx表示长度为x的单调递增序列最后一个元素的最小值,可得fi<fi+1,即fi单调递增
证明
这里我们用反证法,假设有u<v,且fu≥fv,假设有长度为v的单调递增序列a,其中又有最后一个元素是au的长度为u的单调递增子序列,如图
a1,a2,……,au,……,av
∵a单调递增
∴fu=au,fv=av
∵fu≥fv
∴au≥av,这与a单调递增不符,所以假设不成立,即当u<v,fu<fv
所以dpi=maxi-1j=0j,fj<bj,这个找j的过程可以用二分
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
int f[N],n=1,b[N];
int main(){
while(cin>>b[n]&&b[n]!=0)//输入
n++;
n--;
int sum=0;
for(int i=1;i<=n;i++){
if(f[sum]<b[i]){//特判最长长度增加的情况
sum++;
f[sum]=b[i];
continue;
}
int l=0,r=sum;
while(l+1<r){
int mid=(l+r)/2;
if(f[mid]>=b[i])
r=mid;
else l=mid;
}
f[r]=min(f[r],b[i]);//更新f[l+1]的值
}
cout<<sum;
return 0;
}
经典问题
P1020 [NOIP1999 普及组] 导弹拦截:https://www.luogu.com.cn/problem/P1020
第一问同上,略作修改即可。第二问是Dilworth 定理(将一个序列剖成若干个单调不升子序列的最小个数等于该序列最长上升子序列的个数),具体第一篇题解有