46.【必备】一维差分与等差数列差分

本文的网课内容学习自B站左程云老师的算法详解课程,旨在对其中的知识进行整理和分享~

网课链接:算法讲解047【必备】一维差分与等差数列差分_哔哩哔哩_bilibili

一.航班预定统计

题目:航班预订统计

算法原理

  • 整体原理
    • 此算法用于计算每个航班预定的座位总数。通过使用差分数组的思想,先根据预订记录对差分数组进行操作,然后计算差分数组的前缀和,最后得到每个航班预订的座位数。
  • 具体步骤
    • 差分数组操作
      • 创建一个长度为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)(表示在lr范围上依次加上首项为s、末项为e、公差为d的数列),对差分数组arr进行如下操作:
        • arr[l]的位置加上首项s,这是该区间起始位置的增加值。
        • arr[l + 1]的位置加上d - s,这是因为从l + 1开始,数列的增加值需要根据公差d和首项s来调整。
        • arr[r + 1]的位置减去d+e,这是为了在r + 1这个位置抵消掉之前在lr区间添加的数列影响,使得从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 = 0xor = 0。通过循环遍历最终的数组arr(从1n),对于每个元素:
        • 通过max = Math.max(max, arr[i])更新最大值max
        • 通过xor ^= arr[i]计算异或和xor
      • 最后将异或和与最大值以规定的格式输出。

代码实现 

// 一开始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];
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值