所有的最长子序列都差不多,只是>,<,>=,<=的区别,所以要注意符号问题,之前学过常规写法,今天又看到了一个二分写法,时间复杂度前者为n*n,后者为nlogn,所以学一学还是很有必要的,挑了一道自己oj上的dp水题
传送门 :
最长不上升子序列
常规写法很简单,也当是复习了
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
int dp[1005];
int a[1005];
int main()
{
int n;
while(scanf("%d",&n) != EOF && n)
{
for(int i = 1; i <= n; i ++){
scanf("%d",&a[i]);
dp[i] = 1;
}
for(int i = 2; i <= n; i ++)
for(int j = 1; j < i; j ++)
if(a[i] <= a[j])
dp[i] = max(dp[i], dp[j] + 1);
int ans = 0;
for(int i = 1; i <= n; i ++)
if(ans < dp[i])
ans = dp[i];
printf("%d\n",ans);
}
return 0;
}
接下来就是二分写法,有点类似于堆栈,如果有元素小于等于栈顶元素,就把该元素堆上,否则就进行“最合适的替换操作”,
一开始没太明白,手动模拟了一下就ok,然而这道题我不会用二分,尴尬不,因为这是最长不上升子序列,所以二分法需要返回序列中小于该元素的最大的元素下标,然而我不会,找了好久也没有搜到。。等我熟悉熟悉再回来
但是我会求最长上升子序列的。。。。
这道题隐藏的有些深,其实就是要求最长上升子序列长度,自己画个图很容易理解
常规写法很简单
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
int num[30005];
int dp[30005];
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i = 1; i <= n; i ++)
{
scanf("%d",&num[i]);
dp[i] = 1;
}
for(int i = 2; i <= n; i ++)
for(int j = 1; j < i; j ++)
if(num[i] > num[j])
dp[i] = max(dp[i], dp[j] + 1);
int ans = 0;
for(int i = 1; i <= n; i ++)
if(ans < dp[i])
ans = dp[i];
printf("%d\n",ans);
}
return 0;
}
二分写法:
其中二分法返回该大于元素的最小值的下标
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
int dp[1005];
int a[1005];
int find_binary(int num, int k)
{
int low = 1, high = k;
while(low <= high)
{
int mid = (low + high) / 2;
if(num >= dp[mid])
low = mid + 1;
else
high = mid - 1;
}
return low;
}
int main()
{
int n;
while(scanf("%d",&n) != EOF && n)
{
for(int i = 1; i <= n; i ++){
scanf("%d",&a[i]);
}
int len = 1;
dp[1] = a[1];
for(int i = 2; i <= n; i ++)
{
if(a[i] > dp[len])
dp[++len] = a[i];
else
{
int pos = find_binary(a[i], len);//寻找小于a[i]的最小数
dp[pos] = a[i];
}
}
printf("%d\n",len);
}
return 0;
}
从这里面认识到了两个新的函数
ForwardIter lower_bound(ForwardIter first, ForwardIter last,const _Tp& val)算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置。
ForwardIter upper_bound(ForwardIter first, ForwardIter last, const _Tp& val)算法返回一个非递减序列[first, last)中第一个大于val的位置。
直接用lower_bound()会比手写的慢一点,但是方便
这道题如果这样写的话代码是这样的:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
int dp[1005];
int a[1005];
int main()
{
int n;
while(scanf("%d",&n) != EOF && n)
{
for(int i = 1; i <= n; i ++){
scanf("%d",&a[i]);
}
int len = 1;
dp[1] = a[1];
for(int i = 2; i <= n; i ++)
{
if(a[i] > dp[len])
dp[++len] = a[i];
else
{
//int pos = find_binary(a[i], len);//寻找小于a[i]的最小数
int pos = upper_bound(dp + 1, dp + 1 + len, a[i]) - dp;
dp[pos] = a[i];
}
}
printf("%d\n",len);
}
return 0;
}