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 数组的范围。