蓝桥杯学习-08序列二分

08序列二分

序列二分应用的序列必须是递增或递减,但可以非严格

只要r是mid-1,就对应mid=(l+r+1)/2

例题1-模板题(18492)

注意这里是个递增的序列。

解答

java 复制代码
import java.util.Scanner;
import java.util.StringTokenizer;
//18492
public class Main{

    public static void main(String[] args){
        int t = 1;
        while(t --> 0) solve();
}
    static int N = (int)(1e5 + 10);
	static int a[] = new int[N];
	static int n;
    
	static int getL(int a[], int l, int r, int x){
	while(l < r){
		int mid = l + r >> 1;
		if(x <= a[mid]){
			r = mid;
		}else{
			l = mid + 1;
	}
}
	if(a[l] != x) return -1;
	return l;
}
    static int getR(int a[], int l, int r, int x){
        while(l < r){
            int mid = l + r + 1 >> 1;
            if(x < a[mid]){
                r  = mid - 1;
}else{
                l = mid;
}
}
        if(a[l] != x) return -1;
        return l;
    }
    
    static int lower_bound(int a[], int l, int r, int x){
        if(a[r] < x)  return -1;
		while(l < r){
		int mid = l + r >> 1;
		if(x <= a[mid]){
			r = mid;
		}else{
			l = mid + 1;
	}
}
		return l;
    }
    
    static int upper_bound(int a[], int l, int r, int x){
		if(a[r] <= x)  return -1;
		while(l < r){
			int mid = l + r >> 1;
			if(x < a[mid]){
				r = mid;
			}else{
				l = mid + 1;
		}
}
			return l;
}
    static void solve(){
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();
    int q = sc.nextInt();
    for(int i = 1; i <= n; i++){
        a[i] = sc.nextInt();
}
    while(q-->0){
        int op = sc.nextInt();
        int l = sc.nextInt();
        int r = sc.nextInt();
        int x = sc.nextInt();
        if(op == 1){
            System.out.println(getL(a,l,r,x));
        }else if(op == 2){
            System.out.println(getR(a,l,r,x));
		}else if(op == 3){
            System.out.println(lower_bound(a,l,r,x));
		}else if(op == 4){
            System.out.println(upper_bound(a,l,r,x));
		}
    }
}

}

例题2-最大通过数(3346)

前缀和加二分的题目

思路:

复制代码
方向思路:
做题很多时候是关注结果而不是关注过过程,所以思考的重点应该是过程,而不是求怎么分配。要从结果来思考问题。
本题,有左右两个山洞,所以两个都是不确定的。但是只要一个山洞确定了闯关多少个门,那另一个也是确定的了。这也是一个很重要的思想啊。
接着就是循环遍历嘛,先遍历一个山洞再遍历另一个山洞,为了降低时间复杂度,就要使用二分。

做法一:暴力做法

java 复制代码
import java.util.*;
public class Main {
    static int N = 200010;
    static int[] a = new int[N];
    static int[] b = new int[N];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //输入n,m,k;
        int n = sc.nextInt();
        int m = sc.nextInt();
        int k = sc.nextInt();
        //输入两个数组并求前缀和
        for(int i = 1; i <= n; i++){
            a[i] = sc.nextInt();
            a[i] += a[i - 1];
}
        for(int i = 1; i <= m; i++){
            b[i] = sc.nextInt();
            b[i] += b[i - 1];
}
        int ans = 0;
        for(int i = 0; i <= n; i++){
            //枚举第一个山洞
            if(a[i] > k){
                break;//两个都闯不了关
}
            int t = k - a[i];
            for(int j = 1; j <= m; j++){
                if(b[j] > t){
                    ans = Math.max(i + j - 1,ans);
                    break;
}
}
}
        System.out.print(ans);
        sc.close();
    }
}

通过了14个用例。

做法二:利用二分的做法

使用upper_bound做法

java 复制代码
import java.util.*;
public class Main {
    static int N = 200010;
    static long[] a = new long[N];
    static long[] b = new long[N];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //输入n,m,k;
        int n = sc.nextInt();
        int m = sc.nextInt();
        long k = sc.nextInt();
        //输入两个数组并求前缀和
        for(int i = 1; i <= n; i++){
            a[i] = sc.nextLong();
            a[i] += a[i - 1];
        }
        for(int i = 1; i <= m; i++){
            b[i] = sc.nextLong();
            b[i] += b[i - 1];
        }
        int ans = 0;
        for(int i = 0; i <= n; i++){
            //枚举第一个山洞
            if(a[i] > k){
                break;//两个都闯不了关
            }
            long t = k - a[i];
            //二分第二个山洞, 找到大于t的前一个数的下标,结果就是l/r+i
            int l = 0,r = m;//l要等于0,因为t可能不够它用,那么第二个人只能闯0关
            while(l < r){
                int mid = l + r + 1 >> 1;
                if(b[mid] > t){
                    r = mid -1;
                }else{
                    l = mid;
                }
            }
            ans = Math.max(l + i, ans);

        }
        System.out.print(ans);
        sc.close();
    }
}
java 复制代码
import java.util.*;
public class Main {
    static int N = 200010;
    static long[] a = new long[N];
    static long[] b = new long[N];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //输入n,m,k;
        int n = sc.nextInt();
        int m = sc.nextInt();
        long k = sc.nextInt();
        //输入两个数组并求前缀和
        for(int i = 1; i <= n; i++){
            a[i] = sc.nextLong();
            a[i] += a[i - 1];
        }
        for(int i = 1; i <= m; i++){
            b[i] = sc.nextLong();
            b[i] += b[i - 1];
        }
        int ans = 0;
        for(int i = 0; i <= n; i++){
            //枚举第一个山洞
            if(a[i] > k){
                break;//两个都闯不了关
            }
            long t = k - a[i];
            //二分第二个山洞, 找到大于t的第一个数的下标,结果就是l/r+i-1
            int l = 0,r = m + 1;
            while(l < r){
                int mid = l + r >> 1;
                if(b[mid] > t){
                    r = mid;
                }else{
                    l = mid + 1;
                }
            }
            ans = Math.max(l + i - 1, ans);

        }
        System.out.print(ans);
        sc.close();
    }
}
复制代码
一定要特别注意数据类型,这里我把ai,bi的数据类型写为int,很多答案错误。
因为计算前缀和,所以应该选为long类型,一下就通过了。

为什么二分选择寻找前一个数的下标就可以百分百通过,寻找第一个数的下标反而就是最后一个用例通不过?
不是这个问题。而是l和r的取值问题。
这个是一个问题。

问题分析
二分查找 (l = 0, r = m) 可能存在问题:

b[mid] > t 这个条件可能导致 r = mid 时跳过了一些情况。
l = mid + 1; 可能会跳过正确的 mid,导致找不到合适的 b[mid] 值。
l + i - 1 计算错误:

l 代表的是 第一个大于 t 的 b[mid] 的索引,但 l 可能等于 m,这时候 b[l] 可能超出界限。
前缀和数组 b 没有正确处理边界情况:

b[m] 是否能被正确访问?
b[0] 是否正确处理?
b 的索引范围是否合适?
修改方案
修正二分查找的边界问题:

使用 l = 0, r = m + 1,确保二分查找的范围是 [0, m]。
使用 while(l < r) 保证 l 是第一个 大于 t 的 b[mid]。
修正 Math.max(l + i - 1, ans) 的计算方式:

l 可能等于 m,所以要检查 l 是否超出 b 数组的范围。
相关推荐
এ旧栎15 分钟前
蓝桥与力扣刷题(蓝桥 星期计算)
java·数据结构·算法·leetcode·职场和发展·蓝桥杯·规律
刘阿去31 分钟前
lua C语言api学习4 编写C模块
c语言·学习·lua
Three~stone2 小时前
Vue学习笔记集--六大指令
vue.js·笔记·学习
电子艾号哲2 小时前
STC89C52单片机学习——第20节: [8-2]串口向电脑发送数据&电脑通过串口控制LED
单片机·学习·电脑
qq_589568102 小时前
java学习笔记3
java·笔记·学习
朱剑君2 小时前
人工智能与机器学习——系统学习规划
人工智能·学习·机器学习
你可以叫我仔哥呀3 小时前
k8s学习记录(三):Pod基础-Node选择
学习·docker·kubernetes
夜猫程序猿3 小时前
算法题刷题方法记录(蓝桥杯、Leetcode)
算法·leetcode·蓝桥杯
one day3213 小时前
Selenium 自动化测试学习总结
学习·selenium·测试工具
EnigmaCoder3 小时前
蓝桥杯刷题周计划(第三周)
学习·算法·蓝桥杯