蓝桥杯[阶段总结] 二分,前缀和
前言
前些天一直没有发每日一题,是因为我最近在系统学习一些常考算法,时不时自己练一下,这样效率更高。其实之前都是看别人的思路,然后写的题解。虽然在一定程度上可以提升对题目的理解,但这样的效率太低了,为了写一道题的题解,花费大量的时间,到最后对于题目涉及到的算法也只是一知半解。
二分
这里就不过多解释定义,按照我的理解就是,定义左右指针,计算中间值是否符合题意,如果符合的话,就把左指针移动到mid上。否则就将右指针移动到mid-1处。我们来看一道真题:分巧克力。
题目描述
儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:
1. 形状是正方形,边长是整数
2. 大小相同例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?
输入
第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
输入保证每位小朋友至少能获得一块1x1的巧克力。
输出
输出切出的正方形巧克力最大可能的边长。
解题思路
题目还是很好理解的,这道题也是典型的二分,和我之前讲的冶炼金属很类似。我们将
所有的边长放在数轴上。如果mid指向的边长切出来的巧克力块数大于等于k的话,那这个位置有可能是答案,就将left移动到此处。反之,如果切出来的巧克力块数小于k,那说明不够分,那就将right移动到mid-1处。
代码实现
import java.util.Scanner;
public class Main {
private static int nums[][];
private static int k;
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
nums = new int[n][2];
k = scan.nextInt();
for (int i = 0; i < n; i++) {
nums[i][0] = scan.nextInt();
nums[i][1] = scan.nextInt();
}
// for (int i = 0; i < n; i++) {
// System.out.println(Arrays.toString(nums[i]));
// }
//处理数据
int left = 1;
int right = 200050;
while (left < right) {
int mid = left + right + 1 >> 1;
//规定:为true时,移动左指针
if (check(mid))
left = mid;
else right = mid - 1;
}
System.out.println(left);
}
private static boolean check(int mid) {
//切割巧克力
int length = nums.length;
int total = 0;
for (int i = 0; i < length; i++) {
total += (nums[i][0] / mid) * (nums[i][1] / mid);
if (total >= k)
return true;
}
return false;
}
}
前缀和
对于一维的来说就简单了,类似数列求和。s[i]就表示从第一个数到第i个数的和。由于是类似数列的,所以就不多解释了。二维前缀和的话,后面遇到题目了再来细讲。
那么我们来看一道基础题。
题目描述
输入一个长度为n的整数序列。
接下来再输入m个询问,每个询问输入一对l,r。
对于每个询问,输出原序列中从第l个数到第r个数的和。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式
共m行,每行输出一个询问的结果。
数据范围
1<l<r<n,
1<n,m<100000,
—1000<数列中元素的值<1000
代码实现
上代码
import java.io.IOException;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int m = scan.nextInt();
int[] nums = new int[n + 100];
int[] s = new int[n + 100];
for (int i = 1; i <= n; i++) {
nums[i] = scan.nextInt();
}
//计算前缀和
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] + nums[i];
}
int l;
int r;
while(m-->0){
l= scan.nextInt();
r= scan.nextInt();
System.out.println(s[r]-s[l-1]);
}
}
}
//这里我自定义了一个输入类,用于快读的。
class Scanner {
private BufferedReader bf;
private StreamTokenizer st;
public Scanner(InputStream inputStream) {
bf = new BufferedReader(new InputStreamReader(inputStream));
st = new StreamTokenizer(bf);
}
public int nextInt() throws IOException {
st.nextToken();
return (int) st.nval;
}
public String nextLine() throws IOException {
return bf.readLine();
}
}