引子
对hdu6703,首先将问题转化为“询问一个排列中大于等于k的值里,下标超过r的最小权值是多少”
我们采用官方题解中的做法:权值线段树+剪枝
对(a[i],i)建线段树,查询权值线段树的[k,n]中第一个下标超过r的值
代码是这样的
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)的了吗?
思考
经过半个月的思考(被虐),我大概懂了这个剪枝
首先分析以下几个问题:
什么情况下才会递归下去?
由代码第三行的
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))的链
深入思考
思考完以上几个问题,继续思考:
当走完这条错误链,回溯的时候,会回溯到哪里?
当然是第一次出现这个错误分叉的地方(其实就是父节点)
但是此时我们在左区间没找到答案,会去右区间,而右区间([mid+1,r])是完全包含于合法区间([k,n])中的,所以只会出现两种情况
1.右区间没有合法答案,O(1)退出,继续回溯
2.右区间有答案,最终答案必在右区间中
一旦出现了2,就是正常的没有限制([k,n])的线段树找最小值的O(logn)的做法了
而1也只是一个普通的回溯,按照父节点回溯到最原始的错误分叉,答案就在另一条路中
这个问题就解决啦
结论
这个剪枝强无敌,最终询问操作的执行次数只有两条链
复杂度为O(logn)