线段树上二分学习笔记
update: 题面描述不准确,原题是一个
在 NOIP 模拟赛里,我遇到了这么一道题:
给你一个相邻两位相差为一的序列,支持两种操作:
- 给区间
[l, r] 加上一个值。- 查询区间
[l, r] 中第一个等于x 的下标,保证原序列满足|(a_i-a_{i-1})|\le 1 。
当时在考场上,我虽然想到了二分,但没想到最小值具有单调性,结果坠机了。考完以后,我琢磨出了一个做法:先暴力建一棵线段树,然后在这棵树上二分找第一个大于等于
换个更形象的说法,其实就是在线段树上游走。基本思路就是直接利用左右子树的信息,判断答案可能在左边还是右边,然后直接往可能有解的那边走。对于查询区间
例题
这道题跟我上面说的差不多,就是多了点小细节。
参考代码
int query(int p,int l,int r,int x)
{
if(tr[p].mn>x)
return INF;// 当前区间最小值都比x大,肯定没戏
if(tr[p].l==tr[p].r)// 走到叶子了
return tr[p].mn<=x?tr[p].l:INF; // 看看这个点行不行
if(tr[id].l>=l&&tr[p].r<=r)
{ // 这个节点完全在查询区间里
if(tr[lt].mn<=x)
return query(rt,l,r,x); // 左边可能有答案
else
return query(rt,l,r,x); // 左边不行就去右边
}
int ans=INF,mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid)
ans=query(lt,l,r,x); // 左边跟查询区间有重叠,优先找i左边
if(ans==INF&&r>mid)
ans=query(rt,l,r,x); // 左边没找到且右边有重叠
return ans;
}
总结
线段树上二分其实就是在线段树的子树里中进行即使决策,优先往左边走,只处理跟查询区间有交集的节点,这样就能高效地找到答案。