【蓝桥杯】二分与前缀和

二分:确定一个区间,使得答案一定在区间中

整数二分:

对mid的更新方式 , 当right = mid 时使用 left + ((right - left)>> 1)

left = mid 时  mid = left + (((right + 1) - left ) >> 1)

import java.util.*;

class Main{
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        int n = sc.nextInt();
        int m = sc.nextInt();
        
        int[] arr = new int[n];
        
        for (int j = 0; j < n; j++){
            arr[j] = sc.nextInt();
        }
        
        for (int i = 0; i < m; i++){
            
            int q = sc.nextInt();
            
            int left = 0;
            int right = n - 1;
            
            while (left < right){
                int mid = left + ((right - left)>>1);
                
                if (arr[mid] >= q){
                    right = mid;
                }else{
                    left = mid + 1;
                }
            }
            
            if (arr[right] == q){
                System.out.print(right+" ");
                
                right = n - 1;
                while(left < right){
                    int mid = left + (((right + 1) - left) >> 1);
                    
                    if (arr[mid] <= q){
                        left = mid;
                    }else{
                        right = mid - 1;
                    }
                }
                
                System.out.println(left);
            }else{
                System.out.println("-1" + " " + "-1");
            }
            
        }
    }
    
    
}

实数二分:

此题二分的关键在于找到一个最初始的可以满足条件的值,在以此值不断收缩边界即可算出答案

比如找到一个值E0,E0以后的所有值都满足不为负的条件,那么就可以收缩右边界,R = mid

import java.util.*;

class Main{
    static int n = 0;
    static int N = 100010;
    static int[] h = new int[N];
    
    public static boolean check(int mid){
        int E = mid;
        
        for (int i = 0; i < n; i++){
            E = 2*E - h[i];
            
            if (E >= 1e5) return true;
            if (E < 0) return false;
        }
        
        return true;
    }
    
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        n = sc.nextInt();
        
        for (int i = 0; i < n; i++){
            h[i] = sc.nextInt();
        }
        
        int left = 0;
        int right = 100000;
        
        while (left < right){
            int mid = (left + right) / 2;
            
            if (check(mid)) right = mid;
            else left = mid + 1;
        }
        
        System.out.print(right);
    }
    
}

import java.util.*;
import java.io.*;

class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);

        int N = 225000;
        int n = sc.nextInt();

        List<int[]> list = new ArrayList<>();

        for (int c = 0; c * c <= n; c++){
            for (int d = c; c * c + d * d <= n; d++){
                list.add(new int[]{c * c + d * d, c , d});
            }

        }
        
        list.sort((o1, o2) -> {
            if (o1[0] != o2[0]) return o1[0] - o2[0]; // 当前集合内不存在,相等的平方和就将较小的和排前面
            return o1[1] - o2[1]; // 否则就将c较小的排前面
        });        

        for (int a = 0; a * a <= n; a++){
            for (int b = a; a * a + b * b <= n; b++){
                int temp = n - a * a - b * b;

                int left = 0;
                int right = list.size() - 1; // 在集合中寻找

                while (left < right){
                    int mid = left + right >> 1;

                    if (list.get(mid)[0] >= temp) right = mid; // 如果当前值大于等于期望值,将右边界收缩
                    else left = mid + 1;

                }

                if (list.get(left)[0] == temp) {
                    System.out.print(a + " " + b + " " + list.get(left)[1] + " " + list.get(left)[2]);
                    return;
                }
            }
        }
    }
}

此题二分的关键在于,当我们求出一个最小的满足条件的正方形的边长后,使左边界收缩去找最大值。

import java.util.*;

class Main{
    static int N = 100010;
    static int n = 0;
    static int k = 0;
    static int[] h = new int[N];
    static int[] w = new int[N];
    
    public static boolean check(int mid){
        int res = 0;
        
        for (int i = 0; i < n; i++){
            res += (h[i] / mid) * (w[i] / mid);
            if (res >= k) return true;
        }
        
        return false;
    }
    
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        n = sc.nextInt();
        k = sc.nextInt();
        
        for (int i = 0; i < n; i++){
            h[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }
        
        int left = 1;
        int right = 100000;
        
        while (left < right){
            int mid = left + (right + 1) >> 1; // mid 是 当前的边长
            
            if (check(mid)) left = mid; // 当找到一个最小满足的值后开始收缩
            else right = mid - 1; // 找到不满足的值,缩小当前值
        }
        
        System.out.print(right);
    }
    
}

前缀和:求一个区间内 第 i 个数到 第 k 个数的和

定义 S0 = 0 , Si = a1 + a2 + ... + ai 可以推出 Si+1 = Si + ai+1 or Si = Si-1 + ai

即 算区间[L,R]的和可以表示为 : SR - SL-1 = aL + aL+1 + ...+ aR

模板如下:

import java.util.*;

class Main{
    static int l = 0;
    static int r = 0;
    static int N = 100010;
    
    static int n = 0;
    static int m = 0;
    
    static int[] a = new int[N]; // 表示原数组
    static int[] s = new int[N]; // 表示前缀和数组
    
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        n = sc.nextInt();
        m = sc.nextInt();
        
        for (int i = 1; i <= n; i++){
            a[i] = sc.nextInt();
            s[i] = s[i-1] + a[i]; // 前n个数的和
        }
        
        
        for (int i = 1; i <= m; i++){
            l = sc.nextInt();
            r = sc.nextInt();
            
            System.out.println(s[r] - s[l-1]);
        }
    }
    
}

二维前缀和 : 求子矩阵的和

前缀和矩阵的i行j列表示原矩阵i行j列到左上角的所有值的和.

模板:

import java.util.*;
import java.io.*;

class Main{
    static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
    static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));

    static int n = 0;
    static int m = 0;
    static int q = 0;

    static int N = 1010;
    static int[][] a  = new int[N][N];
    static int[][] s = new int[N][N];


    public static void main(String[] args) throws IOException {
        String[] temp = bf.readLine().split(" ");

        n = Integer.parseInt(temp[0]);
        m = Integer.parseInt(temp[1]);
        q = Integer.parseInt(temp[2]);


        for (int i = 1; i <= n; i++){
            String[] temp2 = bf.readLine().split(" ");
            for (int j = 1; j <= m; j++) {
                a[i][j] = Integer.parseInt(temp2[j-1]);
                s[i][j] = s[i][j-1] + s[i-1][j] - s[i-1][j-1] + a[i][j];
            }
        }

        for (int i = 1; i <= q; i++) {
            String[] temp3 = bf.readLine().split(" ");

            int x1 = Integer.parseInt(temp3[0]);
            int y1 = Integer.parseInt(temp3[1]);
            int x2 = Integer.parseInt(temp3[2]);
            int y2 = Integer.parseInt(temp3[3]);

            System.out.println(s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1 - 1][y1 - 1]);
        }
    }
}
  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值