【蓝桥杯】练习题目合集(自用)-2

往期:【蓝桥杯】练习题目合集(自用)-1

题单

  1. 并查集 😱

  2. 排序 🎉

  3. 前缀和🎉

  4. 差分🎉

  5. 二分 🥲😿😭🥹😢(太难了太难了太难了,check真难写)


数学问题

模拟/思维

外卖店优先级 184 【排序】

大佬的代码,但是数据过大会爆内存,太难写了,不写了。

点击查看代码
import java.util.*;
//时间点,外卖店编号,是否接到订单,优先级变化,优先级大于5进入缓存,降低到3移出缓存
//接到订单优先级+2,没有订单-1,最小为0,大于5进入缓存
//读入所有数据,放入二维数组(同一时间可以有多个店收到订单),排序,缺少的时间点为0,遍历,然后按照时间点对外卖店进行

//大佬代码:
public class Main {
    public static void main(String[] args) {
        Scanner scan =new Scanner(System.in);
        int N = scan.nextInt();  // N 加外卖店
        int M = scan.nextInt();  // M 条订单
        int T = scan.nextInt();  // T 时间内

        Set<Integer> set= new HashSet<>();  // 缓存
        int[][] dd = new int[T+1][N+1]; // 时间订单
        long[] arr = new long[N+1];  // 店家优先级记录
        for(int i = 0;i < M;i++){
            dd[scan.nextInt()][scan.nextInt()]++;
        }
        for(int i = 1; i <= T; i++){
            for(int j = 1; j <= N ;j++){
                arr[j] += (dd[i][j] > 0 ? dd[i][j] * 2 : -1);
                if(arr[j] < 0) arr[j] =0;
                if(arr[j] > 5)set.add(j);
                if(arr[j] <= 3)set.remove(j);
            }
        }
        System.out.println(set.size());
    }
}

可获得的最小取值【前缀和】

  • 进行了x次第一种选择的元素和为:后x个元素的和
  • 进行了y次第二种选择的元素和为:前2*y个元素的和
  • 产生式子:设进行了p次第一种选择,k-p次第二种选择,sum[i]为前i项和
    • sum[p*2]:选择了p次最小的两项
    • sum[n] - sum[n-(k-p)]:选择了k-p次最大的一项,求k-p个最大项的和,需要用总和sum[n]减去前n-(k-p)个数的和
  • 故有最终答案:sum[p*2] + ( sum[n] - sum[n-(k-p)] )
点击查看代码
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        long[] a = new long[n + 1];
        long[] sum = new long[n + 1];
        for(int i = 1;i <= n;i++) a[i] = sc.nextLong();
        Arrays.sort(a);//升序
        for(int i = 1;i <= n;i++) sum[i] = sum[i - 1] + a[i];
        //需要k次选择,分情况讨论,到底需要选择几次第一次操作,几次二次操作才能最小,设p次第一次操作,k-p次第二次操作
        long ans = Long.MAX_VALUE;
        for(int p = 1;p <= k;p++){
            ans = Math.min(ans, sum[p * 2] + (sum[n] - sum[n - (k - p)]));
        }
        System.out.print(ans);
    }
}

P5638 【CSGRound2】光骓者的荣耀【一维前缀和,防超时、爆内存】

点击查看代码
import java.io.*;

public class Main {
	public static long[] a;
    public static void main(String[] args) throws IOException {
    	StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in),1<<22));
        in.nextToken();
    	int n = (int) in.nval;
    	in.nextToken();
        int k = (int) in.nval;
        a = new long[n];//n个点,实际上只有n-1段路
        long max = 0;
        if(k >= n-1) {//n-1段路,直接到终点
        	System.out.print(0);
        	return;
        }
        for(int i = 1;i <= n-1;i++) {
        	in.nextToken();
            a[i] = (long) in.nval;
        	a[i] = a[i - 1] + a[i];
        	if(k != 0 && i >= k) {//i >= k,覆盖本段完整路径
        		if(a[i] - a[i-k] > max) {
        			max = a[i] - a[i-k];
        		}
        	}
        }
        System.out.print(a[n-1] - max);
    }
}

经典问题

并查集

P1536 村村通【城镇道路连通,连通块,并查集】

1)什么是连通、可达

  • 无向图里:如果从点 A 出发,沿着边走能到达点 B,就说 A 和 B 连通。
  • 一张图可能不是整体连通的,会分成若干个“互相之间走不到”的部分。

2)什么是连通块

  • 连通块:无向图中,一个极大的连通子图(再加任何外面的点就不连通了)。
  • 换句话说:把所有点按“能互相到达”分组,每一组就是一个连通块。

3)连通块数和“最少加多少边”关系
在无向图中,若有x个连通块,要让全图变成连通,最少需要加x-1条边。举个例子,四个点,最少需要三条边,四个点就可以互通。

点击查看代码
import java.util.*;

public class Main{
    public static int[] s;
    public static int[] size;
    //查询
    public static int find(int x){
        if(s[x] != x) s[x] = find(s[x]);
        return s[x];
    }
    //合并
    public static void merge(int x, int y){
        x = find(x);
        y = find(y);
        if(x == y) return;
        if(size[x] < size[y]){//小树挂在大树上
            int tmp = x;
            x = y;
            y = tmp;
        }
        s[y] = x;
        size[x] += size[y];
    }
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n, m;
        n = sc.nextInt();//个数
        int x, y, ans;
        StringBuilder str = new StringBuilder();
        while(n != 0){
            m = sc.nextInt();
            ans = n - 1;//连通块数
            s = new int[n + 1];
            size = new int[n + 1];
            for(int i = 1;i <= n;i++){
                s[i] = i;
                size[i] = 1;
            }
            while(m-- > 0){
                x = sc.nextInt();
                y = sc.nextInt();
                if(find(x) == find(y)) continue;
                else{
                    merge(x, y);
                    ans--;
                }
            }
            str.append(ans).append("\n");
            n = sc.nextInt();//个数
        }
        System.out.print(str);
    }
}

P6691 选择题【带权并查集】不会

堆箱子 3430【带距离(权)并查集】

点击查看代码
//并查集,但是有几何顺序
import java.util.*;

public class Main {
	static int[] parent;
	static int[] size;
	static int[] dist; // dist[x] = x 到 parent[x] 下面的箱子数;find 时会累计到根

	static int find(int x) {
		if (parent[x] == x)
			return x;
		int p = parent[x];
		int r = find(p);
		dist[x] += dist[p]; // 关键:把距离累加到根
		parent[x] = r; // 路径压缩
		return r;
	}

	// 把 a 所在整堆搬到 b 所在整堆上
	static void move(int a, int b) {
		int ra = find(a);
		int rb = find(b);
		if (ra == rb)
			return; // 同一堆不能操作
		// a放在b上面,相当于b是a的上级,因为要找下面有几个,但是递归是向上寻找根节点,所以实际上是b作a的根节点
		parent[ra] = rb;
		dist[ra] = size[rb]; // ra 这堆整体放到 rb 上面,下面增加 rb 堆的数量
		size[rb] += size[ra];
	}

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int q = sc.nextInt();

		parent = new int[n + 1];
		size = new int[n + 1];
		dist = new int[n + 1];

		for (int i = 1; i <= n; i++) {
			parent[i] = i;
			size[i] = 1;
			dist[i] = 0;
		}

		StringBuilder out = new StringBuilder();
		while (q-- > 0) {
			int k = sc.nextInt();
			if (k == 1) {
				int a = sc.nextInt();
				int b = sc.nextInt();
				move(a, b);
			} else {
				int a = sc.nextInt();
				find(a); // 触发压缩并更新 dist[a]
				out.append(dist[a]).append('\n'); // a 下面的箱子数
			}
		}
		System.out.print(out.toString());
	}
}

  • move()函数将两个集合合并,按照根节点记录每个集合的大小,同时记录子节点(a)的dist
  • find()函数,在路径压缩的同时,累加点x到它的父节点之间的距离,累加操作得到x到根节点的距离
    • parent[x]记录的是x的父节点的时候,dist[x]表示二者之间的距离,因为move()中的dist[ra] = size[rb]操作
    • parent[x]记录的是x的根节点的时候,dist[x]表示二者之间的距离,因为在find()递归过程中,将各个叶子节点之间的距离进行相加。

image

排序

瑞士轮 398 【归并排序】

需要注意Arrays.sort(score,1, N + 1,(a, b)->{})这个函数,由于score[0][]并不是有效数据,他是全0的数据,因此在进行排序的时候不能让他参与到排序,否则在得分为0的选手中,他排第一个,一定会引发错误。

此处偷懒使用Arrays,没用归并排序。

点击查看代码
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt() * 2;//人数
        int R = sc.nextInt();//轮数
        int Q = sc.nextInt();//最终名次
        int[][] score = new int[N + 1][3];//成绩0,实力值1,编号2。每轮开始前按照分数重新排序,从高到低
        for(int i = 1;i <= N;i++) score[i][0] = sc.nextInt();//初始成绩
        for(int i = 1;i <= N;i++){
            score[i][1] = sc.nextInt();//实力
            score[i][2] = i;//编号
        }
        while(R-- > 0){
            Arrays.sort(score,1, N + 1,(a, b)->{
                if(a[0] != b[0]) return b[0] - a[0];
                else return a[2] - b[2];
            });//按成绩,降序排序
            for(int i = 1;i <= N;i+=2){
                if(score[i][1] > score[i + 1][1]) score[i][0]++;
                else score[i + 1][0]++;
            }
        }
        Arrays.sort(score,1, N + 1,(a, b)->{
            if(a[0] != b[0]) return b[0] - a[0];
            else return a[2] - b[2];
        });
        System.out.print(score[Q][2]);
    }
}

前缀和

重新排序【前缀和,差分】

  1. 解读题目:在纸上模拟,可以知道在多次询问区间L-R下求区间内的数的和是可以通过预处理前缀和得到;而所有区间和恰好是每个数的频率乘以数字本身
  2. 解题思路:要求最后增加了多少,容易想到对于重叠区间的频率比较高的数自然要乘上最大的数才能得到最多的增长,所以一个基本想法是将每个数统计频率,然后排好序再将所给的数也同样排好序再对应相乘相加,最后再将前后变化相减即可
  3. 解题方法:对于统计频率而且有前缀和,比较容易想到差分数组,差分数组恰好可以配合前缀和得到每个数的频率,这样时间复杂度比起暴力要好不少
点击查看代码
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        long[] a = new long[n + 1];//数据
        long[] s = new long[n + 1];//前缀和
        int[] fq = new int[n + 1];//对于每个数字的出现频率(差分数组)
        for(int i = 1;i <= n;i++){
            a[i] = sc.nextLong();
            s[i] = s[i - 1] + a[i];
        }
        int t = sc.nextInt();
        int l, r;
        long sum1 = 0, sum2 = 0;
        while(t-- > 0){
            l = sc.nextInt();
            r = sc.nextInt();
            sum1 += s[r] - s[l-1];
            fq[l]++;//差分数组,左端点及以后
            if(r < n) fq[r+1]--;//差分数组,右端点后所有,如果是a数组的右边界,防止越界
        }
        for(int i = 1;i <= n;i++) fq[i] = fq[i] + fq[i-1];//求频率数组(差分数组的前缀和)
        Arrays.sort(a, 1, n + 1);//数据升序,左开右闭
        Arrays.sort(fq, 1, n + 1);//频率升序
        //大的数据和大的频率相乘,才能得到最大的结果
        for(int i = 1;i <= n;i++) sum2 += fq[i] * a[i];
        System.out.print(sum2 - sum1);
    }
}

推箱子【一维差分,前缀和】

采用一维差分,步骤:

  • 由于输入的数据是每列缺口的数据,可以每列按行对应累加得到一维差分数组
  • 求前缀和,得到每行的缺口数量
  • 再求前缀和,得到每行缺口数量的前缀和
一维差分
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int t = sc.nextInt();
        long[] a = new long[n + 2];//差分数组 → 数据 → 前缀和
        int l, h;
        for(int i = 1;i <= n;i++) {
        	l = sc.nextInt() + 1;
        	h = sc.nextInt() + 1;
        	a[l]++;
        	a[h+1]--;
        }
        for(int i = 1;i <= n;i++) a[i] = a[i-1] + a[i];
        for(int i = 1;i <= n;i++) a[i] = a[i-1] + a[i];
        
        long max = Long.MIN_VALUE;

        for(int i = 1;i + t - 1 <= n;i++) {
        	max = Math.max(max, a[i + t -1] - a[i-1]);
        }
        System.out.print((long)n * t - max);//如果n、t达到最大,二者相乘达到10^12
    }
}
二维差分【爆内存】
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int t = sc.nextInt();
        long[][] a = new long[n + 2][n + 2];//差分数组 → 数据 → 前缀和
        int l, h;
        for(int i = 1;i <= n;i++) {
//          第i列的缺口从第l个单位到第h个单位(从底部由0开始数)
        	l = sc.nextInt() + 1;//l,i
        	h = sc.nextInt() + 1;//h,i
        	a[l][i]++;
        	a[h+1][i]--;
        	a[l][i+1]--;
        	a[h+1][i+1]++;
        }
        for(int i = 1;i <= n;i++) 
        	for(int j = 1;j <= n;j++) 
            	a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + a[i][j];
        for(int i = 1;i <= n;i++) 
        	for(int j = 1;j <= n;j++) 
            	a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + a[i][j];
        long max = Long.MIN_VALUE;
        for(int i = 1;i + t - 1 <= n;i++) {
        	max = Math.max(max, a[i + t -1][n] - a[i-1][n]);//缺口最大量
        }
        System.out.print((long)n * t - max);
    }
}

P3397 地毯【二维差分】【输入输出量大】

点击查看代码
import java.io.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in),1<<22));
	public static int nextInt() throws IOException {
		in.nextToken();
		return (int)in.nval;
	}
    public static void main(String[] args) throws IOException {
        int n = nextInt();
        int m = nextInt();
        int[][] a = new int[n+2][n+2];//差分数组 → 数据
        int x1, y1, x2, y2;
        while(m-- > 0) {
        	x1 = nextInt();
        	y1 = nextInt();
        	x2 = nextInt();
        	y2 = nextInt();
        	a[x1][y1]++;
        	a[x2+1][y1]--;
        	a[x1][y2+1]--;
        	a[x2+1][y2+1]++;
        }
        StringBuilder str = new StringBuilder();
        for(int i = 1;i <= n;i++) {
        	for(int j = 1;j <= n;j++) {
            	a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + a[i][j];
            	str.append(a[i][j]).append(" ");
            }
        	str.append("\n");
        }
        System.out.print(str);
    }
}

二分

求阶乘 2145【二分,数学】

  • 尾零是2×5相乘得到的,所以只需要计算n!中2和5的因子的数量。又因为n!中2的因子数量远大于5的因子数量,所以只需要计算5的因子数量。
  • 例如25! = 25×...×20×...×15×...×10×...×5×...,其中的25、20、15、10、5分别有2、1、1、1、1共6个因子5,所以尾零有6个。
  • L < R时继续查找,LR是逐渐收敛至L = Rk <= check(N)是递归左半边,最终会找到一个最小的N

1~20的阶乘:

1	1
2	2
3	6
4	24
5	120
6	720
7	5040
8	40320
9	362880
10	3628800
11	39916800
12	479001600
13	6227020800
14	87178291200
15	1307674368000
16	20922789888000
17	355687428096000
18	6402373705728000
19	121645100408832000
20	2432902008176640000

题解代码:

点击查看代码
public class Main {
    public static long check(long mid){
        long count = 0;
        while(mid > 0){
            count += mid / 5;
            mid /= 5;
        }
        return count;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long k = sc.nextLong();
        long l = 0;
        long r = (long)(1e18 * 5);
        long mid = 0;
        while(l < r){
            mid = (l+r)/2;
            if(k <= check(mid)) r = mid;
            else l = mid + 1;
        }
        if(k == check(l)) System.out.print(l);
        else System.out.print(-1);
    }
}

青蛙过河 2097【二分,前缀和,贪心】

二分好难。🥲

image

点击查看代码
import java.util.*;

public class Main {
    public static long[] h;
    public static int n;
    public static boolean check(int k, int x){
        for(int i = 1;i <= n - k;i++){
            //在区间[i, i + k - 1]这个区间的石块高度够不够2x天
            if(h[i + k - 1] - h[i - 1] < 2 * x) return false;
        }
        return true;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        int x = sc.nextInt();
        h = new long[n];//石头高度,n-1  →  求前缀和
        //mid表示猜测的跳远能力
        for(int i = 1;i < n;i++){
            h[i] = sc.nextLong();
            h[i] = h[i-1] + h[i];
        }
        int l = 1;//跳跃能力最小为1
        int r = n;//最远跳过整条河,就是和的宽度n,而不是看数组最大索引n-1
        int mid = 0;
        while(l < r){
            mid = (l + r) / 2;
            if(check(mid, x)) r = mid;
            else l = mid + 1;
        }
        System.out.print(l);
    }
}

管道 3544【二分,区间合并】

  • 先使用二分法来选择时间,然后采用区间合并的方法判断是否覆盖整个管道。
  • 一个阀门在最晚时间S_i = 10^9打开,要从管道一端覆盖到另一端,最多需要扩散len-1的时间,而len最大也可以是10^9,所以最坏情况下覆盖全程的时间大约为S_i + len ≈ 2×10^9
点击查看代码
public class Main {
    public static int n;
    public static int[][] a;
    public static boolean check(int ti, int len){
        int right = 0;
        int L, R;//对于位于Li的阀门,在ti时刻会从L流到R段
        for(int i = 1;i <= n;i++){//遍历每个阀门
            if(ti >= a[i][1]){//在该阀门打开后
                L = a[i][0] - (ti - a[i][1]);//左端点
                R = a[i][0] + (ti - a[i][1]);//右端点
                if(L <= right + 1){//如果这个段的左端点跟上一段的右端点有交集,说明开阀门之后,水可以流到这一段
                    right = Math.max(right, R);//合并水流区间,更新右端点
                }
            }
            if(right >= len) return true;
        }
        return false;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        int len = sc.nextInt();
        a = new int[n + 1][2];//Li,Si
        for(int i = 1;i <= n;i++){
            a[i][0] = sc.nextInt();
            a[i][1] = sc.nextInt();
        }
        int l = 1, r = (int)(2 * 1e9);
        int mid;
        while(l < r){
            mid = (l + r) / 2;
            if(check(mid, len)) r = mid;
            else l = mid + 1;
        }
        System.out.print(l);
    }
}


技能升级 2129【二分,等差数列】

  • 本题考查二分算法,核心是找到第M大的数
  • 给出的n组数据本质上是n个递减的等差数列,首项为A[i],公差为B[i]
  • 可以将这n个等差数列展开,发现核心就是从这些等差数列中找到M个最大的数并累加
  • 因此可以用二分算法找到数列中第M大的数x,然后遍历所有的等差数列,将所有大于等于x的数累加即可
  • 附:本题若暴力求解可以使用优先队列,不断令当前最大数出队并累加,递减后再次入队
  • 如果使用暴力解法可以较快地通过5个测试点,但后5个会超时

血泪教训:check函数的cnt也要是long类型,只改主函数没改check,导致后两个测试点过不去,研究一个小时,结果测数据的时候灵光一现,发现这个cnt好像有点长哦🥲,因为测试数据的公差比较小,测试数据大的时候,cnt一定超过int最大值

如何求可升级次数(数列中不小于x的数据个数):
image

点击查看代码
import java.util.*;
import java.io.*;
//二分算法,找第M大的数
//给出的n组数据本质上是n个递减的等差数列,首项为A[i],公差为B[i]
//因此可以用二分算法在展开的数列中找到第M大的数x,然后遍历所有的等差数列,将所有大于等于x的数累加即可

public class Main {
    public static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in),1<<22));
    public static long[] a, b;
    public static void main(String[] args) throws IOException{
        int n = (int)nextLong();//等差数列个数
        int m = (int)nextLong();//第M大的数
        a = new long[n + 1];
        b = new long[n + 1];
        for(int i = 1;i <= n;i++){
            a[i] = nextLong();
            b[i] = nextLong();
        }
        int l = 0;
        int r = (1000005);//攻击力最大为1e6
        while(l < r){
            int mid = (l + r + 1) / 2;
            //攻击力为mid时,等差数列中有不小于m个数不小于mid,符合条件,找更大的符合条件的mid,使得m次攻击力最大
            if(check(mid, n, m)) l = mid;
            else r = mid - 1;
        }
        int x = l;//第m大的数,最终结果
        //在二分判断中,返回true的条件是cnt >= m,即对于第m大的数x,在数列中有cnt个大于等于x的数
        //但是在计算攻击力之和时,应该先计算大于x的数,最后数量不满m个时,假设差(m-num)个,在加上(m-num)*x即可
        long sum = 0;
        long num = 0;
        for(int i = 1;i <= n;i++){
            if(a[i] > x) {
            	long d = (a[i] - x) / b[i];//数列中大于x的个数
	            if(d * b[i] < a[i] - x) d++;//等差数列的最后一项大于x,因为不是整除
	            long an = a[i] - (d - 1) * b[i];//等差数列最后一项(递减)= 首项 - (项数-1)*公差
	            sum += (an + a[i]) * d / 2;//等差数列求和
	            num += d;
            }
        }
        // 如果剩下的数量不够m个,我们需要加上剩余的 (m - num) 个x
        sum += (m - num) * x;
        System.out.print(sum);
    }
    public static boolean check(int x, int n, int m){//判断不小于x的数据个数是否至少有m个
        long cnt = 0;
        for(int i = 1;i <= n;i++){
            if(a[i] >= x) {
                cnt += (a[i] - x) / b[i] + 1;//求数列中不小于x的数据个数
            }
        }
        return cnt >= m;
    }
    public static long nextLong() throws IOException{
        in.nextToken();
        return (long)in.nval;
    } 
}

跳石头 364【二分,贪心】

点击查看代码
import java.util.*;
//二分,遍历可能的最短跳跃距离d
//贪心,尽可能的去掉小于距离d的石块
//如果去掉的石块个数超过限制,则该距离过大,减小d范围;如果个数在限制以内,增大d范围,接着找更大的符合条件的d
public class Main {
  public static int[] a;
  public static boolean check(int mid, int n, int m){
      int pre = 0, cnt = 0;
      for(int i = 1;i <= n + 1;i++){
          //判断n个石块之间的距离 + 判断目标岸边到最近的石块的距离,如果目标岸边到石块距离小于d也不行
          if(a[i] - pre < mid) cnt++;
          else pre = a[i];
      }
      return cnt <= m;//移走石块不超出数量,返回true
  }
  public static void main(String[] args) {
      Scanner sc = new Scanner(System.in);
      int L = sc.nextInt();//两岸距离
      int n = sc.nextInt();//岩石个数
      int m = sc.nextInt();//可移走最大数
      a = new int[n + 2];//岩石个数为n,但是到两岸之间一共n+1段距离
      a[n + 1] = L;
      for(int i = 1;i <= n;i++) a[i] = sc.nextInt();
      int l = 1, r = (int)1e9;
      while(l < r){
          int mid = (l + r + 1) / 2;
          if(check(mid, n, m)) l = mid;//所有石块的最小间距小于等于d
          else r = mid - 1;
      }
      System.out.print(r);
  }
}

可凑成的最大花束数 3344【check很牛比】

image

点击查看代码
import java.io.*;

public class Main {
	public static int[] a;
	public static boolean check(long mid, int n, int k) {
        long sum = 0;
        for(int i = 1;i <= n;i++){
            sum += (long)Math.min(a[i], mid);//在凑mid束花的过程中,每个人最多贡献mid朵花,防止一束花中有重复的花
        }
		return (sum / k) >= mid;
	}
	public static void main(String[] args) throws IOException{
		StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in), 1 << 22));
		in.nextToken();
		int n = (int)in.nval;// 人
		in.nextToken();
		int k = (int)in.nval;// 一束花需要几朵
		a = new int[n + 1];
		for (int i = 1; i <= n; i++) {
            in.nextToken();
			a[i] = (int)in.nval;
		}
        long l = 0, r = (long)(2 * 1e14);
        while(l < r){
            long mid = (l + r + 1) / 2;
            if(check(mid, n, k)) l = mid;//如果可以包成mid束,再找更大的mid
            else r = mid - 1;
        }
        System.out.print(r);
	}
}

最大通过数 3346【二分,前缀和】

  • 二分遍历关卡个数,check遍历ai个关卡,b(mid - i)个关卡,求二者消耗最少的水晶数,看是否sum <= ki0开始遍历
  • 满足sum <= k,在增大关卡范围,继续查;反之,减小关卡范围
点击查看代码
import java.io.*;

//感觉暴力也能解,应该会超时
//前缀和 + 二分查找

public class Main {
	public static long[] a, b;
	public static boolean check(int mid, long n, long m, long k) {
		Long sum = Long.MAX_VALUE;
		for (int i = 0; i <= mid; i++) {//i <= mid,而不是i <= n,因为一定有mid <= n
			if (i <= n && mid - i <= m) {
				sum = (long) Math.min(sum, a[i] + b[mid - i]);
			}
		}
		return sum <= k;
	}
	public static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in), 1 << 22));
	public static void main(String[] args) throws IOException {
		long n = nextLong();
		long m = nextLong();
		long k = nextLong();
		a = new long[(int) (n + 1)];
		b = new long[(int) (m + 1)];
		for (int i = 1; i <= n; i++) {
			a[i] = nextLong();
			a[i] = a[i - 1] + a[i];// 前缀和
		}
		for (int i = 1; i <= m; i++) {
			b[i] = nextLong();
			b[i] = b[i - 1] + b[i];// 前缀和
		}
		int l = 0, r = (int)(m + n);
		while (l < r) {
			int mid = (int) (l + r + 1) / 2;
			if (check(mid, n, m, k)) l = mid;// 找最大关卡
			else r = mid - 1;
		}
		System.out.print(r);
	}

	public static long nextLong() throws IOException {
		in.nextToken();
		return (long) in.nval;
	}
}

肖恩的苹果林 3683【二分,排序,最小值最大化问题】

  • 数据排序
  • 注意,Arrays.sort(a, 1, n + 1);的排序范围是左闭右开,[1, n + 1)
点击查看代码
import java.io.*;
import java.util.Arrays;
//二分遍历【最近距离】mid,看树坑中距离大于等于mid的坑有几个,如果 cnt >= m,则向右遍历,否则向左遍历
public class Main {
	public static int[] a;
	public static boolean check(int mid, int n, int m) {
      int cnt = 1, pre = a[1];
      for(int i = 2;i <= n;i++){
          if(a[i] - pre >= mid) {
        	  pre = a[i];
              cnt++;
          }
      }
		return cnt >= m;
	}
	public static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in), 1 << 22));
	public static void main(String[] args) throws IOException {
		int n = nextInt();
		int m = nextInt();
		a = new int[n + 1];
		for (int i = 1; i <= n; i++) a[i] = nextInt();
		Arrays.sort(a, 1, n + 1);
		int l = 1, r =(int)1e9;
		while (l < r) {
			int mid = (int) (l + r + 1) / 2;
			if (check(mid, n, m)) l = mid;// 找最大的最近距离
			else r = mid - 1;
		}
		System.out.print(r);
	}
	public static int nextInt() throws IOException {
		in.nextToken();
		return (int) in.nval;
	}
}

求函数零点 4496【小数二分】

存在精度丢失问题,我觉得能过多少测试点靠运气

点击查看代码
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //String[] s = sc.next().split("x^2-");
        //在 Java 的 split() 方法中,参数是一个正则表达式,而不是纯字符串。
        //x^2- 中的 ^ 是正则表达式中的特殊字符(表示“匹配行的开始”),所以需要对其进行转义。
        String s = sc.next();
        double a = Double.parseDouble(s.split("x")[0]);
        double b = Double.parseDouble(s.split("-")[1]);
        
        double l = 0, r = 1001.0;//1x^2-1000 取 r 最大值为31~32之间,但是精度不对
        double eps = 1e-4;
        while(r - l > eps){
            double mid = (l + r) / 2;
            if(a * mid * mid - b >= 0) r = mid;//开口向上,缩小范围,向左遍历
            else l = mid;//向右遍历
        }
        System.out.printf("%.4f",l);
    }
}

P1678 烦恼的高考志愿【二分,最近邻搜索】

点击查看代码
import java.util.*;
import java.io.*;

public class Main {
	public static int[] a, b;
	public static StreamTokenizer in = new StreamTokenizer(
        new BufferedReader(new InputStreamReader(System.in),1 << 22));
	public static int nextInt() throws IOException {
		in.nextToken();
		return (int)in.nval;
	}
	public static void main(String[] args) throws IOException {
        int n = nextInt();
        int m = nextInt();
        a = new int[n + 1];//学校
        b = new int[m + 1];//学生
        int max = 0;
        for(int i = 1;i <= n;i++) a[i] = nextInt(); 
        for(int i = 1;i <= m;i++) b[i] = nextInt(); 
        long ans = 0;
        Arrays.sort(a, 1, n+1);//升序
        for(int i = 1;i <= m;i++) {
        	int l = 1, r = n;
        	while(l < r) {
        		int mid = (l + r) / 2;
        		if (a[mid] >= b[i]) r = mid;//找第一个分数线比自己分数高的学校
                else l = mid + 1;
        	}
        	//System.out.println(i + " " + b[i] + " " + a[l]);
        	if(l == 1) ans += Math.abs(a[l] - b[i]);
        	else ans += Math.min(Math.abs(a[l] - b[i]), Math.abs(a[l - 1] - b[i]));//学校l分高

        }
    	System.out.print(ans);
    }
}

P3743 小鸟的设备【小数二分】【思维】

注意:r - l > 1e-6,这个判断条件精度如果写太高,会超时,比如1e-8

点击查看代码
import java.io.*;

public class Main {
	public static int[] a, b;
	public static StreamTokenizer in = new StreamTokenizer(
			new BufferedReader(new InputStreamReader(System.in),1 << 22));
	public static int nextInt() throws IOException {
		in.nextToken();
		return (int)in.nval;
	}
	public static boolean check(double mid, int n, int p) {
		double sum = 0;
		for(int i = 1;i <= n;i++) {
			if(a[i] * mid > b[i]) {
				sum += a[i] * mid  - b[i];
			}
		}
		return sum <= p * mid;//够充电
	}
	public static void main(String[] args) throws IOException {
        int n = nextInt();//设备个数
        int p = nextInt();//每秒充电个数
        a = new int[n + 1];//消耗
        b = new int[n + 1];//原本存储
        double sum = 0;
        for(int i = 1;i <= n;i++) {
        	a[i] = nextInt();
        	b[i] = nextInt();
        	sum += a[i];
        }
        if(sum <= p) {//所有设备的消耗能量速度总和小于充电器的充电速度,无限使用
        	System.out.print(-1);
        	return;
        }
        double l = 0, r = 1e10;
    	while(r - l > 1e-6) {
    		double mid = (l + r) / 2; //可使用时间,小数二分
    		if (check(mid, n, p)) l = mid; 
            else r = mid;
    	}
    	System.out.print(l);
    }
}

贪心

小蓝的智慧拼图购物【优先队列 + 贪心】【最大化可执行任务数】

按照价格从小到大的顺序,优先使用该价格下的优惠折扣最大的优惠券

价格升序,门槛升序
按照门槛,将所有该价格可以使用的优惠券加入优先队列(大顶堆),使用优惠折扣最大那张。

优先队列里面是:当前这个商品可以使用的、还没被用掉的券
image

点击查看代码
import java.util.*;

public class Main {
  public static void main(String[] args) {
      Scanner sc = new Scanner(System.in);
      int n = sc.nextInt();//拼图个数
      int m = sc.nextInt();//优惠券数量
      long[] p = new long[n];//价格
      long[][] youhui = new long[m][2];//门槛,0是门槛,1是折扣,按照0升序
      for(int i = 0;i < n;i++) p[i] = sc.nextLong();
      for(int i = 0;i < m;i++) youhui[i][0] = sc.nextLong();
      for(int i = 0;i < m;i++) youhui[i][1] = sc.nextLong();
      
      PriorityQueue<Long> q = new PriorityQueue<>((a, b)->{
          return (int)(b - a);
      });//存折扣即可,大顶堆

      Arrays.sort(p);
      Arrays.sort(youhui, (a, b)->{
          return (int)(a[0] - b[0]);
      });

      int j = 0;
      long ans = 0;
      for(int i = 0;i < n;i++){
          while(j < m && youhui[j][0] <= p[i]){//将所有门槛符合的优惠券加入队列
              q.offer(youhui[j][1]);
              j++;
          }
          if(!q.isEmpty()) ans += p[i] - q.poll();
          else ans += p[i];
      }
      System.out.print(ans);
  }
}

肖恩的大富翁 3401【降序排序 + 贪心】

优先取最大n个金币数分配给这n个人,如果平均后这n个人符合富人条件x,则ans++;若平均后不符合富人条件x,则继续i++后,永远不可能再有符合该条件的情况了。因为分母人数在增加,但是金币数逐渐减小(降序排序),平均数只会越来越小。 贪心逻辑是严格单调安全的。

点击查看代码
import java.util.*;
import java.math.*;
//贪心,最大n个金币数,排序。
//选中的V个人的所有金币平均分给这V个人,不是所有人
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();//轮数
        int n, x;
        Integer[] arr = new Integer[100005];
        int ans;
        long sum;
        while(t-- > 0){
            n = sc.nextInt();//人数
            x = sc.nextInt();//分界线
            for(int i = 1;i <= n;i++) arr[i] = sc.nextInt();
            Arrays.sort(arr, 1, n + 1, (a, b)-> Integer.compare(b, a));//降序
            ans = 0;
            sum = 0;
            for(int i = 1;i <= n;i++){
                if(arr[i] >= x){
                    ans++;
                    sum += (long)arr[i];
                }
                else if((sum + (long)arr[i]) / (ans + 1) >= x){//最大前m个人的平均金币数达到富人标准
                    //如果x达到最大值,求和的结果会超出int最大值
                    ans++;
                    sum += arr[i];
                }
            }
            System.out.println(ans);
        }
    }
}

肖恩的排序 3333【降序排序 + 贪心 + 排列组合 + 乘法原理】

贪心逻辑同:小蓝的智慧拼图购物

对两个序列降序排序,从B最大值遍历,只要是大于B最大值的,一定大于B的其他数据,因此可以用于下一个位置计算有多少可用数据时的累加。

  • 计算当前位置i能用多少种数字,由于降序,较大数可以使用的后面较小数一定可以使用
  • 因此计算下一位置i + 1能用几种数字时,在i处可用数字进行-1的基础上继续累加就可以(当前位置用掉一个了,要减掉)
  • 通过j遍历A数组,每次在上一次遍历A的停止处接着遍历就好了,不需要重头遍历,这样A只需要遍历一遍。
点击查看代码
//贪心,排序,在这个位置上最多能放几种(抛去先前用过的)
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        Integer[] a = new Integer[n];
        Integer[] b = new Integer[n];
        for(int i = 0;i < n;i++) a[i] = sc.nextInt();
        for(int i = 0;i < n;i++) b[i] = sc.nextInt();
        Arrays.sort(a, (x, y) -> Integer.compare(y, x));//降序
        Arrays.sort(b, (x, y) -> Integer.compare(y, x));//降序
        //计算当前位置能用多少种数字,由于降序,较大数可以使用的后面较小数一定可以使用
        //因此计算下一位置能用几种数字时,在当前位置可用数字-1的基础上继续累加就可以(当前位置用掉一个了,要减掉)
        int j = 0;
        long count = 0, ans = 1;
        for(int i = 0;i < n;i++){
            while(j < n && a[j] > b[i]){
                j++;
                count++;
            }
            ans *= count;//排列组合,当前位置最多有count种用法,乘法原理
            count--;//下一个位置少一个选择,因为这里用掉了
            ans %= (int)(1e9 + 7);
        }
        System.out.print(ans);
    }
}

蓝桥A梦去游乐园 3256【Arrays自定义排序 + 贪心】【最大化可执行任务数】

在保证所给题目的条件下,保证以下两点优先游玩(最好分两个ArrayList来写,分成正数、负数,这样更清晰,不然Arrays排序规则写的可能出问题

  • 先玩增长精力的(arr[i][1]>0),按照精力限制升序排序(arr[1][0])
  • 在玩消耗精力的(arr[i][1]<0),按照精力消耗降序排序(arr[1][1])
点击查看代码
import java.util.*;
import java.math.*;
//贪心,排序,游玩项目数量最大化(精力值限制低,增加精力值优先)
public 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][2];//项目需要的精力值a,玩完精力值的变化b
        
        for(int i = 0;i < n;i++){
            arr[i][0] = sc.nextInt();//限制
            arr[i][1] = sc.nextInt();//增减
        }
        Arrays.sort(arr, (a, b)->{
            //增加精力值的,先玩限制低的
            if(a[1] > 0 && b[1] > 0) return Integer.compare(a[0], b[0]);//升序,正数小的
            //减少精力值的,先玩消耗少的
            else return Integer.compare(b[1], a[1]);//降序,负数大的(else部分包含全是负数、一正一负两种情况,都需要降序排序)
        });
        int ans = 0;
        for(int i = 0;i < n;i++){
            if(m >= arr[i][0] && m + arr[i][1] > 0){
                m += arr[i][1];
                ans++;
            }
        }
        System.out.print(ans);
    }
}

P1223 排队接水【排序 + 贪心 + 最短作业优先 SJF】

一个升序完成。

点击查看代码
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[][] arr = new int[n + 1][2];
        for(int i = 1;i <= n;i++) {
        	arr[i][0] = i;
        	arr[i][1] = sc.nextInt();
        }
        Arrays.sort(arr, 1, n+1, (a, b)->{
        	if(a[1] == b[1]) return a[0] - b[0];
        	else return a[1] - b[1];
        });
        long sum = 0;//所有人的等待时间之和
        for(int i = 1;i < n;i++) {//最后一个人接水没人等着
        	sum += (long)arr[i][1] * (n - i);//第i个人接水,后面的都在等arr[i][1]时间,需要乘以人数
        }
        double ans = sum * 1.0 / n;
        for(int i = 1;i <= n;i++) {
        	System.out.print(arr[i][0] + " ");
        }
        System.out.printf("\n%.2f", ans);
    }
}

P2813 母舰【排序 + 田忌赛马型贪心】

  • 用强攻击去打强防御
  • 能破就优先破
  • 剩下的攻击全部打本体(计入伤害)
点击查看代码
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int[] a = new int[n];
        int[] b = new int[m];
        for(int i = 0;i < n;i++) a[i] = sc.nextInt();//防御
        for(int i = 0;i < m;i++) b[i] = sc.nextInt();//攻击
        Arrays.sort(a);
        Arrays.sort(b);//升序
        int j = 0, i = 0;
        int ans = 0;
        while(i < n && j < m) {
        	if(b[j] > a[i]) {
        		i++;
        		j++;
        	}else {
                ans += b[j];
        		j++;
        	}
        }
        for(int k = j;k < m;k++) {
        	ans += b[k];
        }
        if(i == n) System.out.print(ans);
        else System.out.print(0);
    }
}

P4305 [JLOI2011] 不重复数字【去重 + 大量输入输出】

点击查看代码
import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws NumberFormatException, IOException {
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());
        int n;
        Set<String> set;
        String[] s;
        StringBuilder sb = new StringBuilder();
        while(t-- > 0) {
            n = Integer.parseInt(br.readLine());
            set = new LinkedHashSet<>();
            s = br.readLine().split(" ");
            sb = new StringBuilder();
            for(int i = 0;i < n;i++) set.add(s[i]);
            for(String x : set) {
                sb.append(x).append(" ");
            }
            System.out.println(sb);
        }
    }
}

P1923 【深基9.例4】求第 k 小的数【快排】【大量数据输入】

image

点击查看代码
import java.io.*;

public class Main {
	public static int[] arr;
	public static void main(String[] args) throws IOException {
		StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in), 1 << 22));
        in.nextToken();
        int n = (int) in.nval;
        in.nextToken();
        int k = (int) in.nval;

        arr = new int[n];
        for (int i = 0; i < n; i++) {
            in.nextToken();
            arr[i] = (int) in.nval;
        }
		System.out.print(quickSort(0, n - 1, k + 1));
	}

	public static int quickSort(int l, int r, int k) {
		if (l >= r)
			return arr[l];// 左端点
		int x = arr[l + r >> 1];// 取中点
		int i = l - 1;// 左端点
		int j = r + 1;// 右端点
		int t = 0;
		while (i < j) {
			do {i++;} while (arr[i] < x);
			do {j--;} while (arr[j] > x);
			if (i < j) {
				t = arr[i];
				arr[i] = arr[j];
				arr[j] = t;
			}
		}
		int num_l = j - l + 1;// 左边的个数,含x点
		if (num_l >= k) {
			return quickSort(l, j, k);
		}
		return quickSort(j + 1, r, k - num_l);// 不含x
	}
}
posted @ 2026-02-25 18:20  idle_life  阅读(44)  评论(0)    收藏  举报