對hdu6703,首先將問題轉化爲「詢問一個排列中大於等於k的值裏,下標超過r的最小權值是多少」
咱們採用官方題解中的作法:權值線段樹+剪枝
對(a[i],i)建線段樹,查詢權值線段樹的[k,n]中第一個下標超過r的值
代碼是這樣的spa
int ask(int l, int r, int root){ int mid = (l+r)>>1; if(mx[root]<=R)return -1; if(l==r){return l;} int ans = -1; if(k<=mid)ans=ask(lson); if(ans==-1)ans=ask(rson); return ans; }
這把辣雞的我給看meng了:在R=n的時候,最壞狀況下一直向左遞歸,而且沒找到而後向右遞歸,再向右遞歸的同時又重複沒找到,這個ask不就退化成O(n)的了嗎?code
通過半個月的思考(被虐),我大概懂了這個剪枝
首先分析如下幾個問題:遞歸
由代碼第三行的class
if(mx[root]<=R)return -1;
咱們能夠知道,只有當前權值區間\((l,r)\)的最大下標超過R才可能存在答案查詢
假設當前權值區間爲\([l,r]\)
若是往左邊遞歸沒有O(1)返回的話,根據上面的結論,那麼必定是由於左區間\([l,mid]\)存在一個下標大於R
可是,左區間中合法區間應該爲\([max(k,l),mid]\)
因此當\(k>l\),且答案均分佈在\([l,k]\)時,纔會向左遞歸而且不從左區間返回答案思考
假設最壞狀況,答案在k-1裏,k-1一直在作區間的遞歸中,只有遞歸到當l=k的時候,纔會結束這個錯誤
由線段樹的相關性質只能夠知道,這個最壞狀況能夠到\(l=r=k\),也就是跑了一個\(O(logn)\)的鏈co
思考完以上幾個問題,繼續思考:錯誤
固然是第一次出現這個錯誤分叉的地方(其實就是父節點)
可是此時咱們在左區間沒找到答案,會去右區間,而右區間\([mid+1,r]\)是徹底包含於合法區間\([k,n]\)中的,因此只會出現兩種狀況
1.右區間沒有合法答案,O(1)退出,繼續回溯
2.右區間有答案,最終答案必在右區間中
一旦出現了2,就是正常的沒有限制\([k,n]\)的線段樹找最小值的O(logn)的作法了
而1也只是一個普通的回溯,按照父節點回溯到最原始的錯誤分叉,答案就在另外一條路中
這個問題就解決啦math
這個剪枝強無敵,最終詢問操做的執行次數只有兩條鏈
複雜度爲O(logn)return