题目链接
题目描述
一首歌由 个音符组成,第
个音符持续
拍。音符按顺序演奏,从时刻
开始。例如,第一个音符的演奏区间是
,第二个音符的演奏区间是
,以此类推。
现在有 次询问,每次询问给出一个时刻
,你需要输出该时刻正在演奏的是第几个音符。
解题思路
本题的核心是根据给定的时间点,快速定位其所属的音符区间。如果对每次查询都线性扫描所有音符来累加时长,总时间复杂度会达到 ,可能会超时。
为了优化查询过程,我们可以预处理数据。一个有效的方法是使用前缀和。我们创建一个前缀和数组 ,其中
记录前
个音符的总时长。
根据这个定义,第 个音符(
从 1 开始)的演奏时间区间为
。
对于一个查询时刻 ,我们要找到一个索引
,使得
。这个条件等价于寻找第一个使得
成立的索引
。
由于所有音符的持续时间 都是正数,所以前缀和数组
是一个严格单调递增的序列。这使得我们可以利用二分查找来高效地找到这个索引
。
具体来说,我们可以在前缀和数组上执行 upper_bound
操作。upper_bound(T_j)
会找到数组中第一个值严格大于 的元素。该元素在(从1开始计数的)音符序列中的编号,就是我们要求的答案。
算法步骤:
- 读入
和
。
- 读入
个音符的持续时间,并构建一个长度为
的前缀和数组
。
- 对于
次查询,每次读入一个时刻
。
- 在
数组上使用二分查找,找到第一个大于
的元素的索引,并输出该索引。
代码
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, q;
cin >> n >> q;
vector<long long> prefix_sum(n + 1, 0);
for (int i = 1; i <= n; ++i) {
long long duration;
cin >> duration;
prefix_sum[i] = prefix_sum[i - 1] + duration;
}
for (int i = 0; i < q; ++i) {
long long t;
cin >> t;
// 寻找第一个大于 t 的元素的迭代器
auto it = upper_bound(prefix_sum.begin() + 1, prefix_sum.end(), t);
// 计算该迭代器对应的音符编号(从1开始)
cout << distance(prefix_sum.begin(), it) << endl;
}
return 0;
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int q = sc.nextInt();
long[] prefixSum = new long[n + 1];
prefixSum[0] = 0;
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i - 1] + sc.nextInt();
}
for (int i = 0; i < q; i++) {
long t = sc.nextLong();
int noteIndex = upperBound(prefixSum, t);
System.out.println(noteIndex);
}
}
// 寻找数组中第一个大于 target 的元素的索引
private static int upperBound(long[] arr, long target) {
int left = 1; // 从音符1开始查找
int right = arr.length;
int ans = arr.length; // 如果没找到,说明在最后一个音符之后,但题目保证在范围内
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid] > target) {
ans = mid;
right = mid;
} else {
left = mid + 1;
}
}
return ans;
}
}
import bisect
def main():
n, q = map(int, input().split())
durations = list(map(int, input().split()))
queries = list(map(int, input().split()))
prefix_sum = [0] * n
prefix_sum[0] = durations[0]
for i in range(1, n):
prefix_sum[i] = prefix_sum[i - 1] + durations[i]
for t in queries:
# bisect_right 寻找第一个 > t 的元素的位置(0-indexed)
# 结果加 1 转换为 1-indexed 的音符编号
ans = bisect.bisect_right(prefix_sum, t) + 1
print(ans)
if __name__ == "__main__":
main()
算法及复杂度
- 算法:前缀和 + 二分查找。
- 时间复杂度:
。其中
是构建前缀和数组的时间开销,
是处理
次查询的总时间开销,每次二分查找需要
。
- 空间复杂度:
,用于存储前缀和数组。