什么是最長遞增子序列呢? #include <iostream> using namespace std; int main() { int i,j,n,a[100],b[100],max; while(cin>>n) { for(i=0;i<n;i++) cin>>a[i]; b[0]=1;//初始化,以a[0]結尾的最長遞增子序列長度為1 for(i=1;i<n;i++) { b[i]=1;//b[i]最小值為1 for(j=0;j<i;j++) if(a[i]>a[j]&&b[j]+1>b[i]) b[i]=b[j]+1; } for(max=i=0;i<n;i++)//求出整個數列的最長遞增子序列的長度 if(b[i]>max) max=b[i]; cout<<max<<endl; } return 0; } 顯然,這種方法的時間復雜度仍為o(n^2); #include <iostream> using namespace std; int find(int *a,int len,int n)//若返回值為x,則a[x]>=n>a[x-1] { int left=0,right=len,mid=(left+right)/2; while(left<=right) { if(n>a[mid]) left=mid+1; else if(n<a[mid]) right=mid-1; else return mid; mid=(left+right)/2; } return left; } void fill(int *a,int n) { for(int i=0;i<=n;i++) a[i]=1000; } int main() { int max,i,j,n,a[100],b[100],c[100]; while(cin>>n) { fill(c,n+1); for(i=0;i<n;i++) cin>>a[i]; c[0]=-1;// …………………………………………1 c[1]=a[0];// ……………………………………2 b[0]=1;// …………………………………………3 for(i=1;i<n;i++)// ………………………………4 { j=find(c,n+1,a[i]);// ……………………5 c[j]=a[i];// ………………………………6 b[i]=j;//……………………………………7 } for(max=i=0;i<n;i++)//………………………………8 if(b[i]>max) max=b[i]; cout<<max<<endl; } return 0; } 對于這段程序,我們可以用算法導論上的loop invariants來幫助理解. 列有多個,剛保存末尾元素最小的那個.(這一性質決定是第3條性質成立的前提) 的遞增了序列只有一個,c[1]也是最小的; c進入循環前單調遞增及find函數的性質可知(見find的注釋), 此時c[j+1]>c[j]>=a[i]>c[j-1],所以把c[j]的值更新為a[i]后,c[j+1]>c[j]>c[j-1]的性質仍然成 立,即c仍然是單調遞增的; 小不會變大,因為進入循環前c[j]的值是最小的,則循環中把c[j]更新為更小的a[i],當 然此時c[j]的值仍是最小的; 為j有:c[j-1]<a[i]<=c[j],這說明c[j-1]是小于a[i]的,且以c[j-1]結尾的遞增子序列有最大的 長度,即為j-1,把a[i]接在c[j-1]后可得到以a[i]結尾的最長遞增子序列,長度為(j-1)+1=j; 增子序列的長度均已求出,再通過第8行的循環,即求出了整個數組的最長遞增子序列。 仔細分析上面的代碼可以發現,每次循環結束后,假設已經求出c[1],c[2],c[3],...,c[len]的值,則此時最長遞增子序列的長度為len,因此可以把上面的代碼更加簡化,即可以不需要數組b來輔助存儲,第8行的循環也可以省略。 #include <iostream> using namespace std; int find(int *a,int len,int n)//修改后的二分查找,若返回值為x,則a[x]>=n { int left=0,right=len,mid=(left+right)/2; while(left<=right) { if(n>a[mid]) left=mid+1; else if(n<a[mid]) right=mid-1; else return mid; mid=(left+right)/2; } return left; } int main() { int n,a[100],b[100],c[100],i,j,len;//新開一變量len,用來儲存每次循環結束后c中已經求出值的元素的最大下標 while(cin>>n) { for(i=0;i<n;i++) cin>>a[i]; b[0]=1; c[0]=-1; c[1]=a[0]; len=1;//此時只有c[1]求出來,最長遞增子序列的長度為1. for(i=1;i<n;i++) { j=find(c,len,a[i]); c[j]=a[i]; if(j>len)//要更新len,另外補充一點:由二分查找可知j只可能比len大1 len=j;//更新len } cout<<len<<endl; } return 0; } |
求最長遞增部分序列是一個比較常見的動態規劃題。在導彈攔截等題中都有用到。一般來說就是用經典的O(n^2)的動態規劃算法。 算法如下: 設A[i]表示序列中的第i個數,F[i]表示從1到i這一段中以i結尾的最長上升子序列的長度,初始時設F[i] = 0(i = 1, 2, ..., len(A))。則有動態規劃方程:F[i] = max{1, F[j] + 1} (j = 1, 2, ..., i - 1, 且A[j] < A[i])。 然而在hoj10027中n的值達到了50000。顯而易見經典算法是會超時滴。所以只有另謀出路了。 用一個變量len記錄到目前為止所找出來的最長遞增序列的長度。另外準備一個數組b[],用這個數組表示長度為j的遞增序列中最后一個元素的值。在這里長度為j的遞增序列不止一個,我們所要保存是那個最小的。為什么呢?因為最后一個元素越小,那么這個遞增序列在往后被延長的機會越大。初始化b[0] = -1;len = 0;從第一個元素a[1]開始 a[i]( 1 <= i <= n)。如果這個元素比len長的序列的最大值大。則把這個元素直接添加到b數組的后面。如果這個元素比b數組的第一個元素還要小則把這個元素賦給b數組的第一個值。否則進行二分查找。當在b數組里面找到一個數比a[i]小,并且他的后面的數大于或等于a[i]則跳出。將a[i]添加到這個數的后面。輸出len就可以了。 代碼如下: #include <stdio.h> |