蓝桥杯学习-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 数组的范围。
相关推荐
知识分享小能手1 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
茯苓gao4 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾4 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT5 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
aaaweiaaaaaa5 小时前
HTML和CSS学习
前端·css·学习·html
看海天一色听风起雨落6 小时前
Python学习之装饰器
开发语言·python·学习
speop7 小时前
llm的一点学习笔记
笔记·学习
非凡ghost7 小时前
FxSound:提升音频体验,让音乐更动听
前端·学习·音视频·生活·软件需求
ue星空8 小时前
月2期学习笔记
学习·游戏·ue5