集训第五周动态规划 E题 LIS
Description
The world financial crisis is quite a subject. Some people are more relaxed while others are quite anxious. John is one of them. He is very concerned about the evolution of the stock exchange. He follows stock prices every day looking for rising trends. Given a sequence of numbers p1, p2,...,pn representing stock prices, a rising trend is a subsequence pi1 < pi2 < ... < pik, with i1 < i2 < ... < ik. John’s problem is to find very quickly the longest rising trend.
Input
Each data set in the file stands for a particular set of stock prices. A data set starts with the length L (L ≤ 100000) of the sequence of numbers, followed by the numbers (a number fits a long integer).
White spaces can occur freely in the input. The input data are correct and terminate with an end of file.
White spaces can occur freely in the input. The input data are correct and terminate with an end of file.
Output
The program prints the length of the longest rising trend.
For each set of data the program prints the result to the standard output from the beginning of a line.
For each set of data the program prints the result to the standard output from the beginning of a line.
Sample Input
6
5 2 1 4 5 3
3
1 1 1
4
4 3 2 1
Sample Output
3
1
1
Hint
There are three data sets. In the first case, the length L of the sequence is 6. The sequence is 5, 2, 1, 4, 5, 3. The result for the data set is the length of the longest rising trend: 3.
求解最长上升子序列,可以先看一看dp解法。
既然想要有状态转移方程,就应该有状态表示:可以以dp(i)代表以a【i】为终点的最长上升子序列,那么初始值呢?可以把dp数组的所有值都赋为1,因为1就是上升子序列的最小长度了,如果以后有动态变化,1也不会对判断造成影响,动态转移方程应为dp(i)=max{dp(j)+1,j<i,a[i]>a[j]}。接下来是动规解LIS,注意:这道题不可以,会超时!!!
#include"iostream"
#include"cstring"
#include"algorithm"
using namespace std;
const int maxn = 101000;
int a[maxn],dp[maxn];
int n;
int res()
{
int Max;
//memset(dp,1,sizeof(dp));
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);
Max=*max_element(dp+1,dp+n+1);
return Max;
}
int main()
{
while(cin>>n)
{
for(int i = 1;i<=n;i++) {cin>>a[i];dp[i]=1;}
cout<<res()<<endl;
}
return 0;
}
接下来是这道题的正确解法,O(nlogn)的查找LIS算法。
通过上面的讨论可知LIS dp运行的平均时间是(n^2)/2,也就是O(n^2),这样的算法和朴素的查找LIS有什么区别?
如果想要更高效的解决方案,dp是需要放弃的。首先看一个例子:
0 15 14 18 19 --------------1
很显然LIS长度为4
可以是0 15 18 19或者0 14 18 19
假定我们选择第一种方案
可以看出如果最高位是15,那么比他小的数14就被拒绝了,即14不会对当前LIS的值做出任何贡献
再看一道例子:
15 18 14 0 12 13 -----------2
很显然LIS长度是3假如依然采用上面的方案,18对14 0 12 13都采取了拒绝,那么答案就不对了,也就是说,当前LIS的最高位不一定总能拒绝新的比它小的数。
那么什么时候不能拒绝呢?假定当前LIS的最高位是X,长度是LX,如案例2,X=18,LX=2,那么当它遇到0 12 13这样的序列序列就不能够拒绝,因为0 12 13产生的上升序列长度是3,并不比LX小,它不能够拒绝不低于它自身所产生的效益。就像有人给你30元,你能够拒绝其它低于30元的诱惑,当有人给出50,答案就不一定了,你需要做的是舍弃掉之前的LIS,这样才是最好的方案。
加一个样例:
0 11 15 14 7 8 9
0
0 11
0 11 15
0 11 14
0 7 14
0 7 8
0 7 8 9
可以看出,并非是要出现一个全新的比原来LIS长的序列时才进行替代,任何比他小的序列也可以取代,如0 7 8完全替代了0 11 15,这样也是为了保证拒绝的数尽可
能地少。
因此,我们可以建立一个数组,维护它的单调(不递减)性,每次出现一个数都用二分查找找到它在这个数组中的位置,当然,如果它大于当前LIS最大值,累加一位.
这样就实现了如果出现一个新的上升序列且长于已保存的,那么就会完全覆盖掉之前的上升序列。很显然,这样的写法只能得到LIS的长度,无法得到正确LIS的序列,
这是因为在LIS未完全替代旧的LIS就对旧的LIS进行了值的覆盖,如果想要同时得到LIS的正确序列,应该再开一个数组用于存储答案,然后在数组长度增加时对
单调数组进行检查,看是否完全替代,如果是就直接存储当前LIS,如果否就把存储旧的LIS加上当前增加到数组尾部的这个值。当然这样的“检查”时间耗费高,不如直
接用dp的好。。。
#include"iostream"
#include"cstring"
#include"cstdio"
using namespace std;
const int maxn=1e5+10;
int ans[maxn],n;
void Work()
{
int temp;
memset(ans,0,sizeof(ans));
int top=0;
ans[0]=-1;
for(int i=1;i<=n;i++)
{
cin>>temp;
if(temp>ans[top])
ans[++top]=temp;
else
{
int r,l,m;
l=1;r=top;
while(l<=r)
{
m=(l+r)>>1;
if(ans[m]>=temp) r=m-1;
else l=m+1;
}
ans[l]=temp;
}
}
cout<<top<<endl;
}
int main()
{
while(cin>>n)
{
Work();
}
return 0;
}