#P2395. 第1题-监考
-
1000ms
Tried: 386
Accepted: 67
Difficulty: 3
所属公司 :
华为
时间 :2023年4月19日-暑期实习
-
算法标签>差分数组
第1题-监考
题目大意
塔子哥是一名监考机器人,能够同时监控多个考场。每个考场有不同的考试开始时间和结束时间。监控一个考场的费用是每分钟 3金币;监控两个或以上的考场时,费用是每分钟 4金币;如果没有监控任务,则每分钟消耗 1金币。要求计算塔子哥在一天的监考任务中能够获得多少金币。
思路
知识点学习:Oi-Wiki 前缀和&差分
题目意思本质为:一维正半轴上给n条线段。每个整点i有一个值vi,大小根据i点覆盖的线段个数coveri 来定.
coveri=0,vi=1
coveri=1,vi=3
coveri>1,vi=4
然后求从最左边线段开始,到最右边线段结束这个区间的vi 之和。
暴力做法
由于区间的范围有限,可以开数组cover[i]。所以一个显然的暴力做法是:
1.枚举所有线段,循环让其覆盖的点 cover[i]++ .
2.最后扫一遍cover 数组,根据上述条件得到vi 再求和即可。复杂度爆炸
优化
<暴力做法>中的第一步可以用差分数组解决:对于区间[l,r] , 我们cover[l]++,cover[r+1]−− 。 然后所有区间处理完之后,对cover 做前缀和 即可。 (可以自己纸上模拟这个过程体会一下原理)
代码说明
1.输入处理:
读取考场的数量 n,并记录每个考场的开始时间和结束时间。
2.初始化边界:
找到所有考场的最早开始时间和最晚结束时间,以便确定时间范围。
3.构建差分数组:
使用差分数组来表示每个时间点被监控的考场数量。 对于每个考场的时间段 [a_i, b_i],在 cover[a_i] 增加 1,cover[b_i + 1] 减少 1。
4.前缀和计算:
对差分数组进行前缀和计算,得到每个时间点的覆盖数量 cover[i]。
5.计算金币收入:
根据覆盖的数量计算在时间段内的收入。 如果 cover[i] == 0,收入增加 1;如果 cover[i] == 1,收入增加 3;如果 cover[i] > 1,收入增加 4。
6.输出结果:
输出塔子哥在监考过程中的总收入。
差分数组
代码(C++/Java/Python/Go/Js)
CPP
#include <bits/stdc++.h>
using namespace std;
int main() {
int n; // 考场数量
cin >> n; // 读取考场数量
// 读入数据
vector<vector<int>> lines(n, vector<int>(2)); // 存储每个考场的开始和结束时间
int start = INT_MAX, end = INT_MIN; // 初始化最早开始时间和最晚结束时间
// 获取最早开始时间和最晚结束时间
for (int i = 0; i < n; i++) {
cin >> lines[i][0] >> lines[i][1]; // 读取考场的开始和结束时间
start = min(start, lines[i][0]); // 更新最早开始时间
end = max(end, lines[i][1]); // 更新最晚结束时间
}
// 差分数组
vector<int> cover(end + 2); // 创建差分数组,大小为最晚结束时间+2
for (auto& line : lines) {
cover[line[0]]++; // 在开始时间位置增加覆盖数量
cover[line[1] + 1]--; // 在结束时间的下一个位置减少覆盖数量
}
// 前缀和
for (int i = start; i <= end; i++) {
if (i == 0) { // 如果时间为0,跳过不处理
continue;
}
cover[i] += cover[i - 1]; // 计算前缀和,得到每个时间点的覆盖数量
}
// 根据题意计算金币收入
int sum_v = 0; // 初始化收入总和
for (int i = start; i <= end; i++) {
if (cover[i] == 0) {
sum_v += 1; // 如果没有覆盖,收入增加1金币
} else if (cover[i] == 1) {
sum_v += 3; // 如果只有一个考场被覆盖,收入增加3金币
} else {
sum_v += 4; // 如果两个或多个考场被覆盖,收入增加4金币
}
}
cout << sum_v << endl; // 输出总收入
return 0; // 程序结束
}
python
# 读取考场数量
n = int(input())
# 读取每个考场的开始和结束时间,并存储在列表中
lines = [list(map(int, input().split())) for i in range(n)]
# 获取最早开始时间和最晚结束时间
start = min([x[0] for x in lines]) # 找到所有考场的最早开始时间
end = max([x[1] for x in lines]) # 找到所有考场的最晚结束时间
# 创建差分数组,大小为最大结束时间+2
cover = [0] * (end + 2) # 差分的右端点需要多一位
# 根据考场的开始和结束时间更新差分数组
for line in lines:
cover[line[0]] += 1 # 在开始时间位置增加覆盖数量
cover[line[1] + 1] -= 1 # 在结束时间的下一个位置减少覆盖数量
# 计算前缀和以获得每个时间点的覆盖数量
for i in range(start, end + 1):
if i == 0: # 如果时间为0,跳过
continue
cover[i] += cover[i - 1] # 更新当前时间点的覆盖数量
# 根据覆盖数量计算金币收入
sum_v = 0 # 初始化收入总和
for i in range(start, end + 1):
if cover[i] == 0:
sum_v += 1 # 如果没有考场覆盖,收入增加1金币
elif cover[i] == 1:
sum_v += 3 # 如果只有一个考场被覆盖,收入增加3金币
else:
sum_v += 4 # 如果两个或多个考场被覆盖,收入增加4金币
# 输出总收入
print(sum_v)
Java
import java.util.Scanner; // 引入 Scanner 类,用于读取输入
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in); // 创建 Scanner 对象
int n = sc.nextInt(); // 读取考场数量
int[][] lines = new int[n][2]; // 创建二维数组存储每个考场的开始和结束时间
int start = Integer.MAX_VALUE; // 初始化最早开始时间为最大值
int end = Integer.MIN_VALUE; // 初始化最晚结束时间为最小值
// 获取最早开始时间和最晚结束时间
for (int i = 0; i < n; i++) {
lines[i][0] = sc.nextInt(); // 读取开始时间
lines[i][1] = sc.nextInt(); // 读取结束时间
start = Math.min(start, lines[i][0]); // 更新最早开始时间
end = Math.max(end, lines[i][1]); // 更新最晚结束时间
}
int[] cover = new int[end + 2]; // 创建差分数组,大小为最大结束时间 + 2
// 差分操作
for (int[] line : lines) {
cover[line[0]]++; // 在开始时间位置增加覆盖数量
cover[line[1] + 1]--; // 在结束时间的下一个位置减少覆盖数量
}
// 前缀和
for (int i = start; i <= end; i++) {
if (i == 0) {
continue; // 如果时间为 0,跳过不处理
}
cover[i] += cover[i - 1]; // 更新当前时间点的覆盖数量
}
// 根据题意计算金币收入
int sum_v = 0; // 初始化收入总和
for (int i = start; i <= end; i++) {
if (cover[i] == 0) {
sum_v++; // 如果没有覆盖,收入增加 1金币
} else if (cover[i] == 1) {
sum_v += 3; // 如果只有一个考场被覆盖,收入增加 3金币
} else {
sum_v += 4; // 如果两个或多个考场被覆盖,收入增加 4金币
}
}
System.out.println(sum_v); // 输出总收入
sc.close(); // 关闭 Scanner
}
}
Go
package main
import (
"bufio" // 引入 bufio 包,用于高效输入输出
"fmt" // 引入 fmt 包,用于格式化输入输出
"math" // 引入 math 包,用于数学计算
"os" // 引入 os 包,用于操作系统功能
)
const N = 1e6 + 10 // 定义常量 N,表示差分数组的大小
var n int // 考场数量
func main() {
in := bufio.NewReader(os.Stdin) // 创建输入读取器
out := bufio.NewWriter(os.Stdout) // 创建输出写入器
defer out.Flush() // 在函数结束时刷新输出
fmt.Fscan(in, &n) // 读取考场数量
var start, end int // 定义开始和结束时间
left, right := math.MaxInt32, math.MinInt32 // 初始化最早和最晚时间
diff := make([]int, N) // 创建差分数组
// 获取最早开始时间和最晚结束时间 + 差分
for i := 0; i < n; i++ {
fmt.Fscan(in, &start, &end) // 读取每个考场的开始和结束时间
left = min(left, start) // 更新最早开始时间
right = max(right, end) // 更新最晚结束时间
diff[start]++ // 在开始时间位置增加覆盖数量
diff[end+1]-- // 在结束时间的下一个位置减少覆盖数量
}
ans, cur := 0, 0 // 初始化总收入 ans 和当前覆盖数量 cur
// 前缀和 + 求和
for i := 0; i <= right; i++ {
cur += diff[i] // 计算当前时间点的覆盖数量
if i >= left { // 只在有效时间范围内计算收入
if cur == 0 {
ans++ // 如果没有考场覆盖,收入增加 1金币
} else if cur == 1 {
ans += 3 // 如果只有一个考场被覆盖,收入增加 3金币
} else {
ans += 4 // 如果两个或多个考场被覆盖,收入增加 4金币
}
}
}
fmt.Fprintln(out, ans) // 输出总收入
}
// 辅助函数:返回较小的两个整数中的最小值
func min(a, b int) int {
if a < b {
return a
}
return b
}
// 辅助函数:返回较大的两个整数中的最大值
func max(a, b int) int {
if a > b {
return a
}
return b
}
Js
process.stdin.resume(); // 开始读取标准输入
process.stdin.setEncoding('utf-8'); // 设置输入编码格式为 UTF-8
let input = ""; // 用于存储输入数据
process.stdin.on("data", (data) => {
input += data; // 将每次读取的数据追加到 input 中
});
// 定义求解函数
function solver(input) {
let diffArr = new Array(1000000 + 2).fill(0); // 初始化差分数组,大小为 1000002
let maxEnd = -1; // 初始化最大结束时间
let minStart = 1000000 + 2; // 初始化最早开始时间
// 处理每个时间区间
for (let interval of input) {
const [start, end] = interval.split(" "); // 读取开始时间和结束时间
// 更新差分数组
diffArr[parseInt(start)] += 1; // 在开始时间位置增加覆盖数量
diffArr[parseInt(end) + 1] -= 1; // 在结束时间的下一个位置减少覆盖数量
maxEnd = Math.max(parseInt(end), maxEnd); // 更新最大结束时间
minStart = Math.min(parseInt(start), minStart); // 更新最早开始时间
}
let nowValue = 0; // 当前覆盖数量
let count = 0; // 总收入初始化为 0
for (let i = minStart; i <= maxEnd; i++) {
// 前缀和计算
nowValue += diffArr[i]; // 计算当前时间点的覆盖数量
// 根据覆盖数量计算收入
if (nowValue == 0) count += 1; // 如果没有覆盖,收入增加 1
else if (nowValue == 1) count += 3; // 如果只有一个考场被覆盖,收入增加 3
else count += 4; // 如果两个或多个考场被覆盖,收入增加 4
}
return count; // 返回总收入
}
process.stdin.on("end", () => {
const inputArr = input.split("\n"); // 按行分割输入
const n = parseInt(inputArr[0]); // 读取考场数量
const intervalArr = inputArr.slice(1, 1 + n); // 获取考场的时间区间
console.log(solver(intervalArr)); // 调用求解函数并输出结果
});
题目内容
小明是一个强大严厉的监考机器人,有他监考的考场总能抓到很多不不听话的学生,小明”手眼通天“,能够同时仔细的观察很多考场,每个学生的一举一动他都能尽收眼底。
很多学校都想使用小明监督考试,而小明的使用成本也非常昂贵,当只监督一个考场时,每监视一分钟收费3金币;同时监视两个以上的考场,每监视一分钟收费4金币;当处于两个监考任务之间的空隙之间时,小明会进入待机状态,每分钟消耗1金币。也就是说,直到完成最后一个监考任务之前,小明机器人都会持续消耗金币。
今天小明又来监考,他今天一共要监视n个不同的考场,每个考场的考试开始时间和结束时间都不同,为简化表达,将时间简化为整数代表的时间单位,时间从0开始。
请你计算小明今天的监考任务能够收取多少金币?
输入描述
第一行一个整数 n 表示小明今天的监考考场数量
接下来 n 行,给出由空格分开的两个整数ai,bi。表示 n 个考场考试的开始时间 ai 与结束时间 bi ,也就是小明开始监考与结束监考的时间(闭区间),保证结束时间大于起始时间
1⩽n⩽10000
0⩽ai,bi⩽106
输出描述
一个整数,代表小明今天监考所能赚取的金币
样例
样例1
输入
3
1 5
4 6
6 6
输出
21