题解:洛谷 P2969 Music Notes
【题目来源】
洛谷:P2969 [USACO09DEC] Music Notes S - 洛谷
【题目描述】
FJ 准备教他的奶牛们演奏一首歌曲。这首歌由 \(N\) 个音符组成,其中第 \(i\) 个音符持续 \(B_i\) 个节拍(\(1 \leq N \leq 50,000\),\(1 \leq B_i \leq 10,000\)),因此整首歌的长度不会超过 500,000,000 个节拍。奶牛们将在时间 0 开始演奏这首歌,因此它们将在时间 0 到时间 \(B_1\) 之前演奏第 1 个音符,在时间 \(B_1\) 到时间 \(B_1 + B_2\) 之前演奏第 2 个音符,依此类推。
然而,最近奶牛们对这首歌失去了兴趣,因为它们觉得这首歌太长且无聊。因此,为了确保奶牛们集中注意力,他向它们提出了 \(Q\) 个问题(\(1 \leq Q \leq 50,000\)),问题的形式为「在时间 \(T\) 到时间 \(T+1\) 之前的区间内,你应该演奏哪个音符?」奶牛们需要你的帮助来回答这些问题,这些问题以 \(T_i\) 的形式给出(\(0 \leq T_i \leq \text{end_of_song}\))。
考虑这首由三个音符组成的歌曲,音符的持续时间分别为 2、1 和 3 个节拍:
Beat: 0 1 2 3 4 5 6 ...
|----|----|----|----|----|----|--- ...
1111111111 : :
22222: :
333333333333333:
这里有一组五个查询及其对应的答案:
Query Note
2 2
3 3
4 3
0 1
1 1
【输入】
* 第 1 行:两个用空格分隔的整数:\(N\) 和 \(Q\)
* 第 2 行到第 \(N+1\) 行:第 \(i+1\) 行包含一个整数:\(B_i\)
* 第 \(N+2\) 行到第 \(N+Q+1\) 行:第 \(N+i+1\) 行包含一个整数:\(T_i\)
【输出】
* 第 1 行到第 \(Q\) 行:对于每个查询,输出一个整数,表示奶牛们应该演奏的音符的索引。
【输入样例】
3 5
2
1
3
2
3
4
0
1
【输出样例】
2
3
3
1
1
【核心思想】
-
问题分析:给定 \(N\) 个音符的持续时间 \(B_i\),以及 \(Q\) 个查询时间 \(T_i\),求每个查询时刻正在演奏的音符编号。音符按顺序连续排列,第 \(i\) 个音符占据时间区间 \([a_{i-1}, a_i)\),其中 \(a_i\) 为前 \(i\) 个音符的持续时间前缀和。本质上是前缀和 + 整数二分问题:在前缀和数组中查找 \(T_i\) 所在的区间。
-
算法选择:
- 前缀和预处理:\(a[i] = a[i-1] + B_i\),表示前 \(i\) 个音符的总持续时间
- 二分查找:对每个查询 \(T_i\),在前缀和数组中二分查找第一个满足 \(a[mid] > T_i\) 的位置,该位置即为答案
-
关键步骤:
- 读取数据:读入 \(N, Q\) 和 \(N\) 个音符持续时间
- 前缀和:\(a[i] = a[i-1] + B_i\)(\(i = 1\) 到 \(N\))
- 处理查询(\(i = 1\) 到 \(Q\)):
- 读入 \(T_i\)
- 二分查找(\(l = 1, r = N\)):
- \(mid = l + (r-l)/2\)
- 若 \(a[mid] \leq T_i\):\(l = mid + 1\)
- 否则:\(r = mid - 1\)
- 输出 \(l\)(\(l\) 为第一个满足 \(a[l] > T_i\) 的位置,即 \(T_i\) 所在的音符编号)
- 输出所有查询结果
-
时间/空间复杂度:
- 时间复杂度:\(O(N + Q \log N)\),前缀和 \(O(N)\),每次查询 \(O(\log N)\)
- 空间复杂度:\(O(N)\),前缀和数组
-
前缀和 + 二分查找的核心思想:
- 区间定位:前缀和数组 \(a[i]\) 将时间轴划分为连续区间,\(T_i\) 落在第 \(k\) 个音符当且仅当 \(a[k-1] \leq T_i < a[k]\)
- 二分查找的单调性:前缀和数组严格递增(\(B_i \geq 1\)),满足二分条件
- 边界处理:二分结束后 \(l\) 指向第一个 \(a[l] > T_i\) 的位置,恰好是 \(T_i\) 所在的音符编号
- 与
upper_bound的等价性:代码中的二分逻辑等价于upper_bound(a+1, a+n+1, T_i) - a,即第一个大于 \(T_i\) 的元素位置 - 适用于区间定位、前缀查询、时间轴类问题
【解题思路】

【算法标签】
普及- #整数二分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, q, a[50005],b[50005];
int main()
{
cin >> n >> q; // 输入n和q
for (int i=1; i<=n; i++) {
int tmp;
cin >> tmp;
a[i] = a[i-1]+tmp; // 使用前缀和记录
}
for (int i=1; i<=q; i++) { // 记录q个问题
cin >> b[i];
}
for (int i=1; i<=q; i++) { // 遍历q个问题
int x = b[i];
int l=1, r=n; // 二分搜索模板
while (l<=r) {
int mid = l + (r-l)/2;
if (a[mid]<=x) { // 当mid对应的值小于等于x
l = mid+1; // 左指针右移至mid+1
} else r = mid-1; // 否则右指针左移至mid-1
}
cout << l << endl; // (输出什么感觉就是个玄学,正常先移动左指针,最后输出的就是r,这里输出l)
}
return 0;
}
【运行结果】
3 5
2
1
3
2
3
4
0
1
2
3
3
1
1
浙公网安备 33010602011771号