题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 个音符的持续时间 \(B_i\),以及 \(Q\) 个查询时间 \(T_i\),求每个查询时刻正在演奏的音符编号。音符按顺序连续排列,第 \(i\) 个音符占据时间区间 \([a_{i-1}, a_i)\),其中 \(a_i\) 为前 \(i\) 个音符的持续时间前缀和。本质上是前缀和 + 整数二分问题:在前缀和数组中查找 \(T_i\) 所在的区间。

  2. 算法选择

    • 前缀和预处理\(a[i] = a[i-1] + B_i\),表示前 \(i\) 个音符的总持续时间
    • 二分查找:对每个查询 \(T_i\),在前缀和数组中二分查找第一个满足 \(a[mid] > T_i\) 的位置,该位置即为答案
  3. 关键步骤

    • 读取数据:读入 \(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\) 所在的音符编号)
    • 输出所有查询结果
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N + Q \log N)\),前缀和 \(O(N)\),每次查询 \(O(\log N)\)
    • 空间复杂度:\(O(N)\),前缀和数组
  5. 前缀和 + 二分查找的核心思想

    • 区间定位:前缀和数组 \(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
posted @ 2026-08-17 09:47  团爸讲算法  阅读(10)  评论(0)    收藏  举报