[分治] lower/upper bound(非STL)
lower/upper bound
有些時候我們需要知道有序數列中第一個大於或等於k(lower bound)的數或者第一個大於k的數(upper bound),如果我們一個一個查找時間復雜度是O(n)的,在STL(Algorithm庫)中有兩個函數lower_bound,和upper_bound,函數支持以上操作,為了更好地理解以上函數我們將手寫上述函數並講解它的工作原理.
[算法描述]
我們假設我們所需要查找的數組是a[]對於每個函數我們都設置3個變量,第一個是左端點,第二個是右端點,第三個是需要查找的k,邊界都是當前查找的區間(left,right)中都有left<right否則不再繼續查詢.對於lower bound(查找第一個大於或等於k的數)而言,我們每次查找中間的一個數,這個數大於或等於k時我們將right賦值為mid,否則就把left賦值為mid+1(因為此時mid不是解所以我們可以直接從mid+1到right查找下一個解),那麽現在有兩種情況(1)區間內有解,這樣我們找出來的left保存的就是解直接返回left的值即可,(2)區間內無解,此時left=right=r,這時我們規定如果返回r+1即無解所以這時返回left+1即可(其實等價與r+1)(註意由於有可能最後答案為r時可能正好是唯一解,為了解決這個問題我們最後判斷一下當前改點是否小於k如果小於k執行上述操作即可)(當然也可以一開始就判斷一下最後一個數和第一個數試一下是否有解).upper bound 類似,我們在二分的時候把mid>k時把right賦值為mid(因為我們要找的是第一個大於k數值所以這裏並不能等於),其他和lower bound類似.註以上返回的其實是a[]中對應數值所在下標(STL中返回的是叠代器位置).
[代碼(lower bound)]
inline int lower_bound(int l,int r,int k){ //返回在數組a中第一個大於等於k的下標 int left=l,right=r,mid=0; while(left<right){ //設置邊界 mid=left+(right-left)/2; //取終點 if(a[mid]>=k){ //由於我們要找的是第一個大於等於k的下標 //所以中點如果大於或等於k,我們就在(left,mid)中查找是否有一個更小答案 right=mid; }else{ //如果中點小於k,我們就在(mid+1,right)中查找是否具有答案 left=mid+1; } } //由於我們找的是區間(l,r)之間如果中間不存在解,那麽就輸出r+1(經過以上操作後left=r) if(a[left]<k){ //這裏有且只有在無解的時候才會++ left++; } return left; }
[代碼(upper_bound)]
inline int upper_bound(int l,int r,int k){ //返回在數值a中第一個大於k的下標 int left=l,right=r,mid=0; while(left<right){ //設置邊界 mid=left+(right-left)/2; //取中點 if(a[mid]>k){ //由於我們要找的是第一個大於k的下標 //所以中點如果大於k就在(left,mid)中尋找 right=mid; }else{ //如果中點小於k,我們就在(mid+1,right)中尋找是否有答案 left=mid+1; } } if(a[left]<=k){ //只有無解時才會++ left++; } return left; }
[代碼(調用即操作示範)]
/* Name: Lower/Upper Bound Author: FZSZ-LinHua Date: 2018 06 13 Time complexity: O(log n) Algorithm: Divide-and-conquer */ # include "iostream" # include "cstdio" const int maxm=10000+10; int n,m,a[maxm],k; inline int lower_bound(int l,int r,int k){ //返回在數組a中第一個大於等於k的下標 int left=l,right=r,mid=0; while(left<right){ //設置邊界 mid=left+(right-left)/2; //取終點 if(a[mid]>=k){ //由於我們要找的是第一個大於等於k的下標 //所以中點如果大於或等於k,我們就在(left,mid)中查找是否有一個更小答案 right=mid; }else{ //如果中點小於k,我們就在(mid+1,right)中查找是否具有答案 left=mid+1; } } //由於我們找的是區間(l,r)之間如果中間不存在解,那麽就輸出r+1(經過以上操作後left=r) if(a[left]<k){ //這裏有且只有在無解的時候才會++ left++; } return left; } inline int upper_bound(int l,int r,int k){ //返回在數值a中第一個大於k的下標 int left=l,right=r,mid=0; while(left<right){ //設置邊界 mid=left+(right-left)/2; //取中點 if(a[mid]>k){ //由於我們要找的是第一個大於k的下標 //所以中點如果大於k就在(left,mid)中尋找 right=mid; }else{ //如果中點小於k,我們就在(mid+1,right)中尋找是否有答案 left=mid+1; } } if(a[left]<=k){ //只有無解時才會++ left++; } return left; } int main(){ scanf("%d%d",&n,&m); //n個數存入a[]中,m次操作 register int i; int x; for(i=1;i<=n;i++){ scanf("%d",&a[i]); } for(i=1;i<=m;i++){ scanf("%d",&x); //需要查詢的是x printf("%d\n",lower_bound(1,n,x)); //在整個數組中查找(這裏只返回下標無論是否有解) } return 0; }
[分治] lower/upper bound(非STL)