决胜数据结构:差分数组、二分搜索与前缀和的珠联璧合及其实战攻略


前言

根据前面二分法,前缀和,差分数组的学习,在此分享一个综合案例来巩固前面所学的知识


一、二分法,前缀和,差分数组

前缀和
二分法
差分数组

二、综合巩固案例

案例:借教室(AcWing505)

借教室

在大学期间,经常需要租借教室。

大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。

教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。

面对海量租借教室的信息,我们自然希望编程解决这个问题。

我们需要处理接下来 n 天的借教室信息,其中第 i天学校有 ri 个教室可供租借。

共有 m份订单,每份订单用三个正整数描述,分别为 dj,sj,tj,表示某租借者需要从第 sj天到第 tj天租借教室(包括第 sj天和第 tj 天),每天需要租借 dj个教室。

我们假定,租借者对教室的大小、地点没有要求。

即对于每份订单,我们只需要每天提供 dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。

如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。

这里的无法满足指从第 sj天到第 tj天中有至少一天剩余的教室数量不足 dj个。

现在我们需要知道,是否会有订单无法完全满足。

如果有,需要通知哪一个申请人修改订单。

输入格式
第一行包含两个正整数 n,m,表示天数和订单的数量。

第二行包含 n个正整数,其中第 i个数为 ri,表示第 i 天可用于租借的教室数量。

接下来有 m 行,每行包含三个正整数 dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。 每行相邻的两个数之间均用一个空格隔开。天数与订单均用从 1开始的整数编号。

输出格式
如果所有订单均可满足,则输出只有一行,包含一个整数 0。否则(订单无法完全满足)输出两行,第一行输出一个负整数 −1,第二行输出需要修改订单的申请人编号。

数据范围
1≤n,m≤10e6,
0≤ri,dj≤10e9,
1≤sj≤tj≤n
输入样例:

4 3
2 5 4 3
2 1 3
3 2 4
4 2 4

输出样例:

-1
2

示例代码(注解详细,适合初学者食用)

#include<bits/stdc++.h> // 引入C++标准库的头文件,包含了大部分常用函数和数据结构

using namespace std; // 使用标准命名空间,避免在调用标准库函数时频繁书写std::

typedef long long  ll; // 定义长整型别名ll,方便后续代码中使用
const int N=1000010; // 定义常量N,作为数组的最大容量

int n, m; // n表示天数,m表示时间段的数量
int a[N], s[N], t[N], d[N]; // a[]存储每天允许的最大教室数量,s[]和t[]存储时间段的起止天数,d[]存储每个时间段占用的教室数
ll b[N]; // 辅助数组b[], 用于存放每天累计的教室占用情况(差分数组)

// check函数用于验证在选择前mid个时间段时,是否满足每天的教室资源限制
bool check(int mid) {
    memset(b, 0, sizeof(b)); // 清零辅助数组b[]
    for(int i = 1; i <= mid; i++) { // 遍历前mid个时间段
        b[s[i]] += d[i]; // 对于每个时间段,在开始天数的位置累加占用的教室数
        b[t[i] + 1] -= d[i]; // 在结束天数的下一天位置减去占用的教室数,形成差分数组
    }

    // 前缀和计算每天的教室实际占用数量,与规定的教室数量进行比较
    ll sum = 0; // 初始化累计和sum为0
    for(int i = 1; i <= n; i++) { // 遍历每一天
        sum += b[i]; // 对b[]数组进行前缀和计算,得到第i天的教室实际占用数量
        
        if(sum > a[i]) return false; // 如果某一天的教室占用数量超过了允许的最大数量,则返回false
    }
    return true; // 若所有天数的教室占用数量均未超过允许的最大数量,则返回true
}

int main() {
    cin >> n >> m; // 输入天数和时间段数量
    for (int i = 1; i <= n; i++) { // 输入每天允许的最大教室数量
        cin >> a[i];
    }
    for(int i = 1; i <= m; i++) { // 输入每个时间段的占用教室数和起止天数
        cin >> d[i] >> s[i] >> t[i];
    }

    // 使用二分查找法找到最多可以安排多少个时间段,满足每天教室资源不超过限制
    int mid, l = 0, r = m;
    while(l < r) {
        mid = (l + r + 1) / 2; // 计算二分查找的中间值
        if(check(mid)) l = mid; // 若前mid个时间段能满足条件,则收缩搜索范围的下界
        else r = mid - 1; // 否则收缩搜索范围的上界
    }

    // 输出结果
    if(r == m) { // 如果没有找到符合条件的解决方案,即r仍为m,表示所有时间段都不能安排
        cout << "0" << "\n";
    } else { // 否则输出最多能安排的时间段数量
        cout << "-1" << "\n" << r + 1;
    }
    return 0; // 主函数正常结束
}

代码详解:
代码的主要功能是解决一类与资源分配有关的问题。在这个场景中,有n天和m个时间段,每天有一定的教室资源限制(数组a[]),每个时间段会在某一天的s[i]t[i]时段内占用d[i]个教室。目标是找出最多可以安排多少个时间段,使得每天的教室资源都不会超过限制。

  1. 首先导入<bits/stdc++.h>头文件,包含了C++标准库的所有内容,方便快速编写程序。
  2. 使用using namespace std;声明使用标准命名空间。
  3. 定义一些基本类型,如长整型ll,以及全局变量n(天数)、m(时间段数),以及三个数组a[](每日可用教室数)、s[](时间段开始日期)、t[](时间段结束日期)、d[](时间段占用教室数)。
  4. check函数接收一个参数mid,表示当前尝试使用的最大时间段数。该函数首先清零辅助数组b[],然后对前mid个时间段,根据差分数组的性质更新b[]:在s[i]位置增加d[i],在t[i]+1位置减少d[i]
  5. 接下来,通过前缀和计算每天的实际教室占用数量sum。循环遍历每一天,累加b[i]sum,并与当天允许的最大教室数a[i]进行比较,如有任何一天超过限制,则返回false,否则返回true
  6. 主函数首先读入输入数据,包括天数、教室限制和各个时间段的信息。
  7. 主函数中使用二分查找的方式来决定最多能安排多少个时间段。初始搜索范围是[0, m],每次将搜索范围的中间值赋给mid,调用check函数判断是否满足条件。如果满足,则收缩上限rmid,否则收缩下限lmid + 1,持续这个过程直到l等于r为止。
  8. 最终输出结果,如果能找到一个可行解(即r != m),则输出-1和最多能安排的时间段数r+1,否则输出0,表示无法在不违反规定的情况下安排任何时间段。

代码利用了差分数组的思想来简化教室资源占用情况的计算,并通过二分查找的方式找到最大的满足条件的时间段数。

  • 29
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值