线段树二分学习笔记
线段树二分学习笔记
只学了一点皮毛,暂且学到什么总结什么。
并不是说用线段树+二分的算法,而是说在线段树的二分分治结构下进行操作。
应用1
权值线段树上二分,找区间第 \(k\) 大/小,这是很经典的应用,和ST表二分/树状数组二分最为接近,不多总结。
全局查询
inline int query(int x,int l,int r,int k)
{
if(l==r)return l;
int mid=l+r>>1;
if(val[x<<1]>=k)return query(x<<1,l,mid,k);
else return query(x<<1|1,mid+1,r,k-val[x<<1]);
}
区间查询
如果是区间查询的话,要比全局查询复杂一些,因为分割出来的区间并不是那么优美。
考虑这样一个问题,记 \(S\) 为前缀的和,在 \([L,R]\) 中找到第一个使得 \(S>v\) ( \(v\) 给定)的位置。
我们不妨记询问的区间为 \([L,R]\),一共在线段树上被分为了 \(m\) 个这样的整区间 \([l_i,r_i]\),显然 \(m\le \log n\)。
从左到右考虑这 \(m\) 个区间,不断将其值累加到 \(S\) 中,直到遇到第一个使得 \(S>v\) 的区间,这样就化归到了“待查区间完全包含线段树区间”的情况,那么就可以直接在这个区间内用类似“全局查询”的方法来做就好。
inline int query(int x,int l,int r,int ql,int qr,int &s,int v)
{
if(ql<=l&&r<=qr)
{
if(val[x]+s<=v)return s+=val[x],-1;//说明当前区间太靠左了,不满足使得S>v的条件
if(l==r)return l;//这时候已经收敛到了答案所在位置,否则进入下面的代码继续查询
}
push_down(x);
int mid=l+r>>1;
if(ql<=mid)
{
int res=query(x<<1,l,mid,ql,qr,s,v);
if(res!=-1)return res;
}//优先递归左子区间,满足“从左到右考虑”
if(qr>mid)
{
int res=query(x<<1|1,mid+1,r,ql,qr,s,v);
if(res!=-1)return res;
}
return -1;
}
应用2
可以解决类似: "找到 最左边/最右边 并且满足 \(val_{pos}\le v\) 的位置 \(pos\)" 的问题。注意,这里 \(val\) 并不一定具有单调性。
这就是看起来不那么“二分”实际上偏向“分治”的“线段树二分”,不需要满足单调性(但也可以二分?),或许还有其他的应用,但是暂时还没学到,日后来补充。
怎么做呢,我们对于线段树上面每一个节点记录一个 \(Min\) 代表这段区间的最小值,当一个节点被查询到的时候,我们优先考虑其左子节点(这里以“最左边”为例)的 \(Min\) 是否满足 \(Min\le v\),如果是的话,就递归进左子结点进行查找;否则相同地去考虑右子节点,如果都无解,那么返回 \(-1\)。
inline int query(int x,int l,int r,int ql,int qr,ll lim)
{
if(val[x]>lim)return -1;
if(l==r)return l;
int mid=l+r>>1;
pd(x);
if(ql<=mid)
{
int res=query(x<<1,l,mid,ql,qr,lim);
if(res!=-1)return res;
}
if(qr>mid)
{
int res=query(x<<1|1,mid+1,r,ql,qr,lim);
if(res!=-1)return res;
}
return -1;
}
更一般的,结合线段树维护的不同信息,线段树二分大概能做到求出 “第一个/最后一个 满足 某某条件” 的位置,例题:
本文来自博客园,作者:Hanggoash,转载请注明原文链接:https://www.cnblogs.com/Hanggoash/p/19026094

浙公网安备 33010602011771号