一、问题求解
1.DP O(n^2)
比较基础的DP,直接上状态转移方程:
dp [ i ] = max { dp [ j ] + 1 ,dp [ i ] } (1 <= j < i,a[ j ] < a[ i ])
int DP(int a[],int n){
int ans = 0;
for(int i=1;i<=n;i++)
dp[i] = 1;
for(int i=1; i<=n; i++)
for(int j=1; j<i; j++)
if(a[j] < a[i])
dp[i] = max(dp[i], dp[j]+1);
for(int i=1; i<=n; i++)
ans = max(ans, dp[i]);
return ans;
}
2.贪心+二分 O(nlogn)
对于一个数组,想要求解最长上升子序列,容易想到当序列末尾元素较小时,其后更容易增加较大元素,从而使序列增大。因此,我们仅需维护一个tmp数组(tmp中tmp [ i ]处储存当上升子序列长度为i时,末尾元素的最小值,由此可知tmp数组长度即为最长上升子序列长度),当a [ i ] > tmp[ len ] 时,将 len++,tmp [ len ] = a [ i ],否则使用a [ i ]更新数组tmp,二分查找a [ i ]可插入的位置,即第一个大于等于a [ i ]的tmp[ j ],并用a [ i ] 更新tmp [ j ]。
代码如下:
int LIS(int a[],int n){
tmp[1] = a[1];
int len = 1;
for(int i = 2; i <= n; i++)
if(a[i]>tmp[len])
tmp[++len] = a[i];
else
tmp[upper_bound(tmp + 1,tmp + len + 1,a[i])-tmp] = a[i];
return len;
}
二、标记数组
因为一般acm竞赛一般关注算法复杂度,因此一般都使用第二种复杂度为O(nlogn)的算法,因此我仅用这个算法求标记数组。使用f数组维护a [ i ]位置的数当前的最长上升子序列长度,然后使用ans数组从后往前遍历一遍,k1 = len,当f [ i ] = k1时,若k1==len,直接将i添加到ans[ len ],否则判断a[ i ]小于ans[ len + 1 ]时将i添加到ans[ len ],len--。ans数组中从下标1到len中储存的就是最长上升子序列的下标。
代码如下:
int LIS(int a[],int n){
tmp[1] = a[1];
int len = 1;
f[1] = 1 ;
for(int i = 2; i <= n; i++){
if(a[i]>tmp[len]){
tmp[++len] = a[i];
f[i] = len;
}
else{
int m = upper_bound(tmp + 1,tmp + len + 1,a[i])-tmp;
tmp[m] = a[i];
f[i] = m;
}
}
int k1 = len;
int maxx = 0x3f3f3f3f;
for(int i = n; i > 0; i--){
if(k1<=0)
break;
if(f[i] == k1 && maxx > a[i]){
ans[k1] = i;
k1--;
maxx = a[i];
}
}
return len;
}