对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