本文的网课内容学习自B站左程云老师的算法详解课程,旨在对其中的知识进行整理和分享~
一.航班预定统计
题目:航班预订统计
算法原理
-
整体原理
- 此算法用于计算每个航班预定的座位总数。通过使用差分数组的思想,先根据预订记录对差分数组进行操作,然后计算差分数组的前缀和,最后得到每个航班预订的座位数。
-
具体步骤
- 差分数组操作
- 创建一个长度为
n + 2
的数组cnt
作为差分数组(多开两个空间是为了方便计算)。对于预订表中的每一条预订记录bookings[i]=[firsti, lasti, seatsi]
,在差分数组中进行如下操作:在cnt[firsti]
的位置加上seatsi
,这表示从firsti
这个航班开始增加了seatsi
个座位预订;在cnt[lasti + 1]
的位置减去seatsi
,这是因为从lasti+1
开始,之前增加的seatsi
个座位预订就不再有效了。
- 创建一个长度为
- 计算前缀和
- 使用
for
循环计算差分数组cnt
的前缀和。对于i
从1到cnt.length - 1
,通过cnt[i]+=cnt[i - 1]
来计算前缀和。这个前缀和的意义是到第i
个航班(这里的i
对应差分数组的索引)累计的座位预订数。
- 使用
- 得到最终结果
- 创建一个长度为
n
的数组ans
用于存储最终结果。通过循环将cnt[i + 1]
的值赋给ans[i]
(因为cnt
数组比实际航班数多开了两个空间),这样ans
数组就包含了每个航班预定的座位总数,最后返回ans
数组。
- 创建一个长度为
- 差分数组操作
代码实现
// 航班预订统计
// 这里有 n 个航班,它们分别从 1 到 n 进行编号。
// 有一份航班预订表 bookings ,
// 表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi]
// 意味着在从 firsti 到 lasti
//(包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。
// 请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。
// 测试链接 : https://leetcode.cn/problems/corporate-flight-bookings/
public class Code01_CorporateFlightBookings {
// bookings
// [1,5,6]
// [2,9,3]
// ...
public static int[] corpFlightBookings(int[][] bookings, int n) {
int[] cnt = new int[n + 2];
// 设置差分数组,每一个操作对应两个设置
for (int[] book : bookings) {
cnt[book[0]] += book[2];
cnt[book[1] + 1] -= book[2];
}
// 加工前缀和
for (int i = 1; i < cnt.length; i++) {
cnt[i] += cnt[i - 1];
}
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
ans[i] = cnt[i + 1];
}
return ans;
}
}
二.三步必杀
题目:三步必杀
算法原理
- 整体原理
- 这个算法主要解决在给定区间上加上等差数列后,求整个范围数字的最大值和异或和的问题。通过设置差分数组的方式来高效处理在区间上添加等差数列的操作,然后构建数组得到最终的数值数组,再从这个数组中求出最大值和异或和。
- 具体步骤
- 差分数组操作(set方法)
- 在
set
方法中,对于给定的操作(l, r, s, e, d)
(表示在l
到r
范围上依次加上首项为s
、末项为e
、公差为d
的数列),对差分数组arr
进行如下操作:- 在
arr[l]
的位置加上首项s
,这是该区间起始位置的增加值。 - 在
arr[l + 1]
的位置加上d - s
,这是因为从l + 1
开始,数列的增加值需要根据公差d
和首项s
来调整。 - 在
arr[r + 1]
的位置减去d+e
,这是为了在r + 1
这个位置抵消掉之前在l
到r
区间添加的数列影响,使得从r + 1
开始不再受该等差数列的影响。 - 在
arr[r + 2]
的位置加上e
,这是为了正确处理在r
位置的数值,因为在arr[r + 1]
位置减去了d + e
,需要在r + 2
位置加上e
来平衡。
- 在
- 在
- 构建最终数组(build方法)
- 在
build
方法中,通过两次循环计算数组arr
的前缀和。第一次循环arr[i]+=arr[i - 1]
,将差分数组转换为初步的数值数组;第二次循环再次执行arr[i]+=arr[i - 1]
,进一步调整数值数组,得到经过所有操作后的最终数值数组。
- 在
- 计算最大值和异或和
- 在
main
方法中,初始化变量max = 0
和xor = 0
。通过循环遍历最终的数组arr
(从1
到n
),对于每个元素:- 通过
max = Math.max(max, arr[i])
更新最大值max
。 - 通过
xor ^= arr[i]
计算异或和xor
。
- 通过
- 最后将异或和与最大值以规定的格式输出。
- 在
- 差分数组操作(set方法)
代码实现
// 一开始1~n范围上的数字都是0,一共有m个操作,每次操作为(l,r,s,e,d)
// 表示在l~r范围上依次加上首项为s、末项为e、公差为d的数列
// m个操作做完之后,统计1~n范围上所有数字的最大值和异或和
// 测试链接 : https://www.luogu.com.cn/problem/P4231
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Code02_ArithmeticSequenceDifference {
public static int MAXN = 10000005;
public static long[] arr = new long[MAXN];
public static int n, m;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while (in.nextToken() != StreamTokenizer.TT_EOF) {
n = (int) in.nval;
in.nextToken();
m = (int) in.nval;
for (int i = 0, l, r, s, e; i < m; i++) {
in.nextToken(); l = (int) in.nval;
in.nextToken(); r = (int) in.nval;
in.nextToken(); s = (int) in.nval;
in.nextToken(); e = (int) in.nval;
set(l, r, s, e, (e - s) / (r - l));
}
build();
long max = 0, xor = 0;
for (int i = 1; i <= n; i++) {
max = Math.max(max, arr[i]);
xor ^= arr[i];
}
out.println(xor + " " + max);
}
out.flush();
out.close();
br.close();
}
public static void set(int l, int r, int s, int e, int d) {
arr[l] += s;
arr[l + 1] += d - s;
arr[r + 1] -= d + e;
arr[r + 2] += e;
}
public static void build() {
for (int i = 1; i <= n; i++) {
arr[i] += arr[i - 1];
}
for (int i = 1; i <= n; i++) {
arr[i] += arr[i - 1];
}
}
}
三.一群人落水后求每个位置的水位高度
题目:Lycanthropy
算法原理
代码实现
// 一群人落水后求每个位置的水位高度
// 问题描述比较复杂,见测试链接
// 测试链接 : https://www.luogu.com.cn/problem/P5026
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Code03_WaterHeight {
// 题目说了m <= 10^6,代表湖泊宽度
// 这就是MAXN的含义,湖泊最大宽度的极限
public static int MAXN = 1000001;
// 数值保护,因为题目中v最大是10000
// 所以左侧影响最远的位置到达了x - 3 * v + 1
// 所以右侧影响最远的位置到达了x + 3 * v - 1
// x如果是正式的位置(1~m),那么左、右侧可能超过正式位置差不多30000的规模
// 这就是OFFSET的含义
public static int OFFSET = 30001;
// 湖泊宽度是MAXN,是正式位置的范围
// 左、右侧可能超过正式位置差不多OFFSET的规模
// 所以准备一个长度为OFFSET + MAXN + OFFSET的数组
// 这样一来,左侧影响最远的位置...右侧影响最远的位置,
// 都可以被arr中的下标表示出来,就省去了很多越界讨论
// 详细解释看set方法的注释
public static int[] arr = new int[OFFSET + MAXN + OFFSET];
public static int n, m;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while (in.nextToken() != StreamTokenizer.TT_EOF) {
// n有多少个人落水,每个人落水意味着四个等差数列操作
n = (int) in.nval;
in.nextToken();
// 一共有多少位置,1~m个位置,最终要打印每个位置的水位
m = (int) in.nval;
for (int i = 0, v, x; i < n; i++) {
in.nextToken(); v = (int) in.nval;
in.nextToken(); x = (int) in.nval;
// v体积的朋友,在x处落水,修改差分数组
fall(v, x);
}
// 生成最终的水位数组
build();
// 开始收集答案
// 0...OFFSET这些位置是辅助位置,为了防止越界设计的
// 从OFFSET+1开始往下数m个,才是正式的位置1...m
// 打印这些位置,就是返回正式位置1...m的水位
int start = OFFSET + 1;
out.print(arr[start++]);
for (int i = 2; i <= m; i++) {
out.print(" " + arr[start++]);
}
out.println();
}
out.flush();
out.close();
br.close();
}
public static void fall(int v, int x) {
set(x - 3 * v + 1, x - 2 * v, 1, v, 1);
set(x - 2 * v + 1, x, v - 1, -v, -1);
set(x + 1, x + 2 * v, -v + 1, v, 1);
set(x + 2 * v + 1, x + 3 * v - 1, v - 1, 1, -1);
}
public static void set(int l, int r, int s, int e, int d) {
// 为了防止x - 3 * v + 1出现负数下标,进而有很多很烦的边界讨论
// 所以任何位置,都加上一个较大的数字(OFFSET)
// 这样一来,所有下标就都在0以上了,省去了大量边界讨论
// 这就是为什么arr在初始化的时候要准备OFFSET + MAXN + OFFSET这么多的空间
arr[l + OFFSET] += s;
arr[l + 1 + OFFSET] += d - s;
arr[r + 1 + OFFSET] -= d + e;
arr[r + 2 + OFFSET] += e;
}
public static void build() {
for (int i = 1; i <= m + OFFSET; i++) {
arr[i] += arr[i - 1];
}
for (int i = 1; i <= m + OFFSET; i++) {
arr[i] += arr[i - 1];
}
}
}