问题:

拿POJ 2533来说。

Sample Input

7
1 7 3 5 9 4 8

Sample Output(最长上升/非降子序列的长度)

4

 

解法一(O(n^2)):

如何把这个问题分解成子问题呢?经过分析,发现 “求以ak(k=1, 2, 3…N)为终点的最长上升子序列的长度”是个好的子问题――这里把一个上升子序列中最右边的那个数,称为该子序列的“终点”。虽然这个子问题和原问题形式上并不完全一样,但是只要这N个子问题都解决了,那么这N个子问题的解中,最大的那个就是整个问题的解。

由上所述的子问题只和一个变量相关,就是数字的位置。因此序列中数的位置k 就是“状态”,而状态 k 对应的“值”,就是以ak做为“终点”的最长上升子序列的长度。这个问题的状态一共有N个。状态定义出来后,转移方程就不难想了。假定MaxLen (k)表示以ak做为“终点”的最长上升子序列的长度,那么:

MaxLen (1) = 1

MaxLen (k) = Max { MaxLen (i):1<i < k 且 ai < ak且 k≠1 } + 1

这个状态转移方程的意思就是,MaxLen(k)的值,就是在ak左边,“终点”数值小于ak,且长度最大的那个上升子序列的长度再加1。因为ak左边任何“终点”小于ak的子序列,加上ak后就能形成一个更长的上升子序列。

实际实现的时候,可以不必编写递归函数,因为从 MaxLen(1)就能推算出MaxLen(2),有了MaxLen(1)和MaxLen(2)就能推算出MaxLen(3)……

 

解法二(O(nlog(n))):

这个算法其实已经不是DP了,有点像贪心。至于复杂度降低其实是因为这个算法里面用到了二分搜索。本来有N个数要处理是O(n),每次计算要查找N次还是O(n),一共就是O(n^2);现在搜索换成了O(logn)的二分搜索,总的复杂度就变为O(nlogn)了。

这个算法的具体操作如下(by RyanWang):

开一个栈,每次取栈顶元素top和读到的元素temp做比较,如果temp > top 则将temp入栈;如果temp < top则二分查找栈中的比temp大的第1个数,并用temp替换它。 最长序列长度即为栈的大小top。

这也是很好理解的,对于x和y,如果x < y且Stack[y] < Stack[x],用Stack[x]替换Stack[y],此时的最长序列长度没有改变但序列Q的''潜力''增大了。

举例:原序列为1,5,8,3,6,7

栈为1,5,8,此时读到3,用3替换5,得到1,3,8; 再读6,用6替换8,得到1,3,6;再读7,得到最终栈为1,3,6,7。最长递增子序列为长度4。

最终栈里的元素不是符合要求的子序列,所以该方法不能实现打印。

 

 

#include <iostream>
#include <vector>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <assert.h>
using namespace std;std::vector<int> v;
std::vector<int> seq;
int len;
int record[10000];
int before[10000];
int stack[10000];
int stackSize;
bool bujian=false;
void input(){
cin >> len;
for(int i = 0 ; i < len ; i++){
int a;
cin >> a;
v.push_back(a);
record[i] = 0;
before[i] =-1;
}
stackSize = 0;
}void output(){
cout << len<<endl;
for(int i = 0 ; i < v.size() ; i++){
cout << v[i]<<" ";

}
}//O(n^2)
int algorithm1(){
record[0] = 1;

for (int i = 1 ; i < len; i++){
for (int j = 0 ; j < i; j++){
if (bujian){
if (v[j] <= v[i] && (record[i] < (record[j]+1)) ){//不减版本
record[i] = record[j]+1;
before[i] = j;
}
}
else{
if (v[j] < v[i] && (record[i] < (record[j]+1)) ){//不减版本
record[i] = record[j]+1;
before[i] = j;
}
}

}
} int maxx = -1;
int finalIndex; for (int i = 0 ; i < len; i++){
if (record[i] > maxx){
maxx = record[i];
finalIndex = i;
}
} while(finalIndex!=-1){
seq.push_back(v[finalIndex]);
finalIndex = before[finalIndex];
}
reverse(seq.begin(), seq.end()); // for (int i = 0 ; i < len; i++){
// cout << record[i]<< " ";
// }
// cout << endl; return maxx;
}//不减版本
int binSearch(int s,int e, int value){

if (e-s == 1){
if (stack[s] > value){
return s;
}
else{
return s+1;
}

}
int middle = (e+s)/2;
if (value == stack[middle]){
if (bujian) while(value == stack[++middle]){}//这里决定是否要递增,或者不减.注释为递增
return middle;
}
if(value < stack[middle]){
assert(s!=middle);
return binSearch(s,middle, value);
}
else{
return binSearch(middle,e, value) ;
}
}//O(nlog(n))
int algorithm2(){

stackSize++;
stack[stackSize-1] = v[0]; for (int i = 1 ; i < len; i++){
if (bujian){
if(v[i] >= stack[stackSize-1] ){//这里决定是否要递增,或者不减.>为递增,>=为不减
stackSize++;
stack[stackSize-1] = v[i];
}
else{
int index = binSearch(0,stackSize,v[i]);
stack[index] = v[i];
}
}
else{
if(v[i] > stack[stackSize-1] ){//这里决定是否要递增,或者不减.>为递增,>=为不减
stackSize++;
stack[stackSize-1] = v[i];
}
else{
int index = binSearch(0,stackSize,v[i]);
stack[index] = v[i];
}
}

} return stackSize;
}int main(){
input();
int a1 = algorithm1();
int a2 = algorithm2();
if (a1!=a2){
cout << "wrong! a1="<<a1<< " a2="<<a2<<endl;
for (int i = 0 ; i < a1 ; i++){
cout << seq[i]<<" ";
}
}
else{
cout <<"max length is " << a1<<endl;
for (int i = 0 ; i < a1 ; i++){
cout << seq[i]<<" ";
}
}
//output();
return 0;
}

黄世宇/Shiyu Huang's Personal Page:​​https://huangshiyu13.github.io/​