求最长上升子序列问题如下:
给定n个整数a1,a2,a3……an,按从左到右的顺序选出尽量多的整数,组成一个上升子序列。例如序列1,6,2,3,7,5,可以选出的最长的上升子序列是1,2,3,5;
分析:
对于线性结构上的dp,我们常常设以某个点结尾能达到的最大/小值;
那么我们可以设dp[i]为以i结尾的最长上升子序列的长度,当到i+1的时候,如果这一位的数字比前面的某一个数字大,那么以那个数字结尾的dp值就可以加1;我们可以用两个for循环来实现统计i从1到我们所要求的n,这样的时间复杂度是O();
另外还有一个复杂度为O(nlogn)的优化方法:
新建一个low数组,low[i]表示长度为i的LIS结尾元素的最小值,很显然,根据贪心的思想,如果我们想要得到最长的LIS,那么这个结尾的元素越小越好,那么我们只要不断地维护low数组就好;
对于每一个a[i],如果a[i]大于当前的low[最长]的结尾元素,那我们把它接到后面,然后更新low[i]的值就好了,否则我们就在low数组中找出第一个大于等于a[i]的元素low[j],用a[i]去更新low[j]就好;由于low数组是递增的,我们可以用二分的方法来完成这个过程;
题目一:POJ - 2533
模板题,给你一串数字序列,让你求出最长上升子序列
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 1005
using namespace std;
int a[maxn];
int low[maxn];//表示长度为i的LIS序列结尾元素的最小值
int main(void)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
low[1]=a[1];
int ans=1;
for(int i=2;i<=n;i++)
{
if(a[i]>low[ans])
low[++ans]=a[i];
else
{
int mid=lower_bound(low,low+ans+1,a[i])-low;//返回的是指向位置的迭代器,减去low之后表示位置,也就是LIS的长度
low[mid]=a[i];
}
}
printf("%d\n",ans);
return 0;
}
题目二:POJ - 1631
题目大意:
就是讲一个芯片公司把一些芯片连错了,对于左右两个芯片,正确的接线不能存在交叉,但是不幸的是线被一个人弄得十分凌乱,出现了很多交叉的情况,芯片公司要把这些线改正确,但是又想尽量达到最小的损失,就是拆掉最少的线,现在给你一种接线情况,问有多少条接线是可以不用被拆掉的。
题目思路:
从图中可以明显看出,如果线不存在交叉,那么对于左边的芯片来说,他的i号管脚接的芯片管脚号一定不能大于i+1、i+2号……管脚对应的右边管脚号。因此,我们可以根据左边管脚从小到大的顺序连线情况得到一串数字,如图中的情况,左边1号接的是4、2号接的是2……可以得到序列:4 2 6 3 1 5,因为左边的是1 2 3 4 5 6,是递增的,根据前面的分析,想要不交叉的话,这个序列也应该是递增的。那么问题就转化成了在得到的这个序列中求最长的上升子序列。也是一个模板题,代码跟上面的都差不多。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define maxn 40005
using namespace std;
int a[maxn];
int low[maxn];
int main(void)
{
int t,n;
scanf("%d",&t);
while(t--)
{
memset(low,0,sizeof(low));
memset(a,0,sizeof(a));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
low[1]=a[1];
int ans=1;
for(int i=2;i<=n;i++)
{
if(a[i]>low[ans])
low[++ans]=a[i];
else
{
int mid=lower_bound(low,low+ans+1,a[i])-low;//返回的是指向位置的迭代器,减去low之后表示位置,也就是LIS的长度
low[mid]=a[i];
}
}
printf("%d\n",ans);
}
return 0;
}
呼呼