线段树上的类二分查找总结

非严格二分查找:

情形一:给定序列a[1]~a[N], 每次询问给定一个数v, 一个位置pos, 从a[pos+1]~a[N]中找到第一个大于v的元素的下标

考虑建立一棵普通的位置线段树, 树上节点维护当前位置区间的最大值; 每次查找时从根递归向下查找, 对于当前区间 [ l,r ]:
0.  若当前节点为叶子结点, 若结点的值满足 > v , 返回下标即可;
1.  若pos <= mid: 若左子树最大值大于v(约束), 则左子树可能存在解, 递归查找左子树; 若左子树查找到解,则直接返回该解(这是一个重要剪枝,可以大幅优化时间, 显然此时即使右子树存在大于v的元素,也不可能是第一个出现的了,所以没必要再查), 否则, 若右子树最大值大于v(约束), 递归查找右子树;
2.  若pos>mid:  若右子树最大值大于v(约束), 递归查找右子树;
tip:不存在解的话返回个INF就行, 代表第一个大于v的元素在右侧无限远处, 也即不存在该元素

例题:HDU - 5875

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mid ((l+r)>>1)
const int maxn = 1e5+10;
const int INF = 1e9+7;
/*此题与hdu6703做法相似, 都是在线段树上查找第一个(位置最左或值最小)满足约束条件的元素!!*/
int n,val[maxn],minn[maxn<<2];
void build(int l,int r,int u){
	if(l==r) {minn[u]=val[l]; return ;}
	build(l,mid,u<<1); build(mid+1,r,u<<1|1);
	minn[u]=min(minn[u<<1],minn[u<<1|1]);
}
int query(int p,int v,int l,int r,int u){
	if(l==r) {return minn[u]<=v?l:INF;}
	int res=INF;
	if(p<=mid){
		if(minn[u<<1]<=v) res=query(p,v,l,mid,u<<1);
		if(res==INF&&minn[u<<1|1]<=v) res=query(p,v,mid+1,r,u<<1|1);
	}else if(minn[u<<1|1]<=v) 
		res=query(p,v,mid+1,r,u<<1|1);
	return res;
}
int main(){
	int t,q,i,j,k,l,r,ans,cur,tmp;
	while(cin>>t){
		while(t--){
			scanf("%d",&n);
			for(i=1;i<=n;i++) scanf("%d",val+i);
			build(1,n,1);		
			scanf("%d",&q);
			for(;q;q--){
				scanf("%d%d",&l,&r);
				ans=val[l]; cur=l+1;
				for(;cur<=r&&ans;cur=tmp+1){
					tmp=query(cur,ans,1,n,1);
					if(tmp<=r) ans%=val[tmp];
				}
				printf("%d\n",ans);
			}
		}
	}
}

情形二:给定序列a[1]~a[N], 每次询问给定一个数v ( 可能需要取离散值 ) , 一个位置pos, 从a[pos+1]~a[N]中找到最小的大于v的元素的值

考虑建立一棵权值线段树, 树上节点维护当前段权值中的最右侧出现的位置(若某个权值不存在,则令其出现位置为0,这样保证了这个数永远不会被查找到); 每次查找时从根递归向下查找, 对于当前区间 [ l,r ]:
1.  若v <= mid: 若左子树中最右位置 > pos(约束), 则左子树可能存在解, 递归查找左子树; 若左子树查找到解,则直接返回该解(重要剪枝,原理同上), 否则, 若右子树中最右位置  >  pos(约束), 递归查找右子树;
2.  若v > mid:  若右子树最右位置大于pos(约束), 递归查找右子树;
tip:不存在解的话返回个INF就行, 代表第最小的大于v的元素是INF, 也即不存在对应元素

例题:HDU - 6703

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
const int MAX = 1e5;
const int INF = 1e8;
/*此题与hdu5875做法相似, 都是在线段树上查找第一个(位置最左或值最小)满足约束条件的元素*/
int a[MAX+5],maxp[(MAX+5)<<2];
set<int> ss;
void add(int p,int v,int l,int r,int u) {
    if(l == r) {maxp[u]=max(maxp[u],v); return ;}
    p <= mid ? add(p,v,l,mid,u<<1) : add(p,v,mid+1,r,u<<1|1);
	maxp[u]=max(maxp[u<<1],maxp[u<<1|1]);
    return ;
}
int query(int p,int v,int l,int r,int u) {
    if(l == r) return maxp[u]>=v?l:INF;
	int res=INF;
    if(p<=mid){
        if(maxp[u<<1]>=v) res=query(p,v,l,mid,u<<1);
        if(res==INF&&maxp[u<<1|1]>=v) res=query(p,v,mid+1,r,u<<1|1);
        return res; 
    }
    else if(maxp[u<<1|1]>=v) 
		res=query(p,v,mid+1,r,u<<1|1);
    return res;
}
int main()
{
    int n,m,k,t,r,op,pos,ans;
//    freopen("data.in","r",stdin);
	cin>>t;
    while(t--) {
        scanf("%d%d",&n,&m);
        ss.clear(); ss.insert(n+1); ans=0; memset(maxp,0,sizeof(maxp));
	    for(int i = 1; i<=n; i++)
	    	scanf("%d",a+i), add(a[i],i,1,n,1);
        while(m--) {
            scanf("%d",&op);
            if(op == 1) {
                scanf("%d",&pos); pos^=ans;
                ss.insert(a[pos]);
            }
            else {
                scanf("%d%d",&r,&k);
                r ^= ans;k ^= ans;
                ans = query(k,r+1,1,n,1);
                int tmp = *ss.lower_bound(k);
                ans = min(ans,tmp);
                printf("%d\n",ans);
            }
        }
    }    
    return 0 ;
}

总结:对于这类问题, 虽然查找的过程非严格的二分, 但由于存在约束条件的限制以及左子树找到解就直接返回的操作,起到了一定程度的剪枝作用,因此仍可以接近二分查找的速度很快的找到解。此外,若找的东西与位置有关(诸如:找右侧第一个满足约束的元素), 则考虑建立位置线段树,每次在满足约束下优先考虑左/右子树; 若找的东西与权值有关(诸如:找右侧最小的满足约束的元素), 则考虑建立权值线段树,每次在满足约束下优先考虑左/右子树。


严格二分查找:

情形三 给定序列a[1]~a[N], 每次查找对应区间内的小于某元素v的值的个数

建立主席树, 每次找到对应的权值线段树, 在当前权值区间 [ l , r] 下,  若v <= mid 则递归统计左子树; 若v > mid , 则递归统计右子树, 同时直接加上左子树的元素个数num即可; 若当前到达叶子结点, 应该判断当前值是否小于v(合法性判断), 满足约束下才返回节点的num, 否则应该返回0

情形三四: 主席树求区间内第k小元素/ 前k小的和(一定注意到叶子结点时不是直接把节点sum累加到答案!), 因为这类问题不存在非法情形(即第k小元素一定存在),所以进入叶子时不需进行合法性判断



 

全部评论

相关推荐

不愿透露姓名的神秘牛友
07-03 16:22
点赞 评论 收藏
分享
避坑恶心到我了大家好,今天我想跟大家聊聊我在成都千子成智能科技有限公司(以下简称千子成)的求职经历,希望能给大家一些参考。千子成的母公司是“同创主悦”,主要经营各种产品,比如菜刀、POS机、电话卡等等。听起来是不是有点像地推销售公司?没错,就是那种类型的公司。我当时刚毕业,急需一份临时工作,所以在BOSS上看到了千子成的招聘信息。他们承诺无责底薪5000元,还包住宿,这吸引了我。面试的时候,HR也说了同样的话,感觉挺靠谱的。于是,我满怀期待地等待结果。结果出来后,我通过了面试,第二天就收到了试岗通知。试岗的内容就是地推销售,公司划定一个区域,然后你就得见人就问,问店铺、问路人,一直问到他们有意向为止。如果他们有兴趣,你就得摇同事帮忙推动,促进成交。说说一天的工作安排吧。工作时间是从早上8:30到晚上18:30。早上7点有人叫你起床,收拾后去公司,然后唱歌跳舞(销售公司都这样),7:55早课(类似宣誓),8:05同事间联系销售话术,8:15分享销售技巧,8:30经理训话。9:20左右从公司下市场,公交、地铁、自行车自费。到了市场大概10点左右,开始地推工作。中午吃饭时间大约是12:00,公司附近的路边盖饭面馆店自费AA,吃饭时间大约40分钟左右。吃完饭后继续地推工作,没有所谓的固定中午午休时间。下午6点下班后返回公司,不能直接下班,需要与同事交流话术,经理讲话洗脑。正常情况下9点下班。整个上班的一天中,早上到公司就是站着的,到晚上下班前都是站着。每天步数2万步以上。公司员工没有自己的工位,百来号人挤在一个20平方米的空间里听经理洗脑。白天就在市场上奔波,公司的投入成本几乎只有租金和工资,没有中央空调。早上2小时,晚上加班2小时,纯蒸桑拿。没有任何福利,节假日也没有3倍工资之类的。偶尔会有冲的酸梅汤和西瓜什么的。公司的晋升路径也很有意思:新人—组长—领队—主管—副经理—经理。要求是业绩和团队人数,类似传销模式,把人留下来。新人不能加微信、不能吐槽公司、不能有负面情绪、不能谈恋爱、不能说累。在公司没有任何坐的地方,不能依墙而坐。早上吃早饭在公司外面的安全通道,未到上班时间还会让你吃快些不能磨蹭。总之就是想榨干你。复试的时候,带你的师傅会给你营造一个钱多事少离家近的工作氛围,吹嘘工资有多高、还能吹自己毕业于好大学。然后让你早点来公司、无偿加班、抓住你可能不会走的心思进一步压榨你。总之,大家在找工作的时候一定要擦亮眼睛,避免踩坑!———来自网友
qq乃乃好喝到咩噗茶:不要做没有专业门槛的工作
点赞 评论 收藏
分享
评论
3
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务