http://poj.org/problem?id=1631
DP 最长上升子序列
最长上升子序列,因为n很大(n<40000),我开始写的O(n^2)的算法超时了。。。
学了一下O(n*logn)的方法,dp[i]表示当前状态下,所有长度为i的子序列中,末尾元素最小的那个子序列,的末尾元素值。
例如序列:a[6]:(1,3,5,4,6,2)
考虑到前6个元素的时候
长度为3的子序列有:(1,3,5) (1,3,4) (1,3,6) (3,5,6) (3,4,6) 那么dp[3] = 4;
长度为4的子序列有:(1,3,5,6) (1,3,4,6) 那么dp[4] = 6;
开始时dp数组为空,依次遍历原序列a[n],对每一个当前元素a[i],都要更新一次dp数组
更新规则:从已有的dp数组中,找到第一个比当前元素a[i]大的元素,返回下标j, 然后更新 dp[j] = a;
到此,总体还是一个O(n*n)的算法,
但因为dp数组是单调的,可以用二分查找,使更新操作变成O(logn)
也可以从后向前查找dp数组,找到后用break;节省大概一半的时间,虽然是O(n^2),也可以A掉本题。。。
O(n*logn)代码:
1 #include <stdio.h> 2 3 int n, a[43210], dp[43210]; 4 const int maxint = (1<<31)-1; 5 6 int bs(int l ,int r, int x) 7 { 8 int m; 9 while(l < r) 10 { 11 m = (l + r)>>1; 12 if(dp[m] < x) 13 { 14 l = m + 1; 15 } 16 else 17 { 18 r = m; 19 } 20 } 21 return l; 22 } 23 24 int LIS() 25 { 26 int i, j, maxn = 1; 27 dp[0] = 0; 28 dp[1] = maxint; 29 for(i=1; i<=n; i++) 30 { 31 j = bs(0, maxn+1, a[i]); 32 dp[j] = a[i]; 33 if(j > maxn) 34 { 35 maxn ++; 36 dp[maxn+1] = maxint; 37 } 38 } 39 return maxn; 40 } 41 42 int main() 43 { 44 int t, i; 45 scanf("%d", &t); 46 while(t-- && scanf("%d", &n)) 47 { 48 for(i=1; i<=n && scanf("%d", a+i); i++); 49 printf("%d\n", LIS()); 50 } 51 return 0; 52 }