在你窗外闪耀的星星

GitHub跳转原题关系图返回列表

先把同一坐标上的星星亮度合并,再把题目转成一维数组上固定长度窗口的最大区间和,用前缀和线性扫描即可。

OJ: luogu

题目 ID: P3353

难度:普及-

标签:前缀和滑动窗口数组模拟

日期: 2026-06-21 01:50

题意

数轴上有 NN 颗星星,第 ii 颗星星在位置 XiX_i,亮度为 BiB_i

窗户一次能看到一个宽度为 WW 的范围,边界上的星星也算被看到。

题目要求我们移动窗户位置,使窗口内所有星星的亮度和最大,并输出这个最大值。

要注意两件事:

  • 同一个位置上可能有多颗星星
  • W=0W=0 时,实际上只能看到某一个精确位置上的星星

思路

先看一个最直接、最容易理解的暴力写法:

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100000 + 5;

int n, w;
int x[MAXN];
int b[MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> w;
    int min_pos = 1000000000;
    int max_pos = 0;

    for (int i = 1; i <= n; i++) {
        cin >> x[i] >> b[i];
        if (x[i] < min_pos) {
            min_pos = x[i];
        }
        if (x[i] > max_pos) {
            max_pos = x[i];
        }
    }

    int answer = 0;

    if (w == 0) {
        // 没有边缘时,只能选中一个精确位置。
        for (int pos = min_pos; pos <= max_pos; pos++) {
            int current = 0;
            for (int i = 1; i <= n; i++) {
                if (x[i] == pos) {
                    current += b[i];
                }
            }
            if (current > answer) {
                answer = current;
            }
        }
    } else {
        int half = w / 2;
        // 枚举窗户中心位置,直接判断每颗星星是否落在窗口内。
        for (int center = min_pos - half; center <= max_pos + half; center++) {
            int left = center - half;
            int right = center + half;
            int current = 0;
            for (int i = 1; i <= n; i++) {
                if (left <= x[i] && x[i] <= right) {
                    current += b[i];
                }
            }
            if (current > answer) {
                answer = current;
            }
        }
    }

    cout << answer << '\n';

    return 0;
}

brute.cpp 的思路很直白:

  • 枚举窗户位置
  • 对每个位置重新扫描所有星星
  • 判断这颗星星是否落在窗口内

这个做法是对的,但会重复扫描很多次,复杂度太高,只适合做理解题意和小数据对拍。

真正的关键是先做一次坐标聚合。

如果同一个位置上有多颗星星,那么无论窗户怎么放,只要覆盖到这个位置,它们的贡献都会一起加进去。

所以可以先定义:

a[x]a[x] 为位置 xx 上所有星星亮度之和

这样题目就从“很多星星”变成了“一个一维数组”:

  • W=0W=0,答案就是 a[x]a[x] 的最大值
  • W>0W>0,答案就是连续 WW 个整数位置上的最大区间和

接下来做前缀和:

prefix_sum[i]=a[1]+a[2]++a[i]prefix\_sum[i] = a[1] + a[2] + \dots + a[i]

于是任意区间 [l,r][l, r] 的亮度和都能在 O(1)O(1) 算出来:

prefix_sum[r]prefix_sum[l1]prefix\_sum[r] - prefix\_sum[l - 1]

最后枚举每个窗口的右端点 rightright

  • 左端点 left=rightW+1left = right - W + 1
  • 当前窗口和就是 prefix_sum[right]prefix_sum[left1]prefix\_sum[right] - prefix\_sum[left - 1]

一路取最大值即可。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXX = 100000 + 5;

int n, w;
int sum_brightness[MAXX];
int prefix_sum[MAXX];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> w;
    int max_pos = 0;

    for (int i = 1; i <= n; i++) {
        int x, b;
        cin >> x >> b;
        sum_brightness[x] += b;
        if (x > max_pos) {
            max_pos = x;
        }
    }

    for (int i = 1; i <= max_pos; i++) {
        prefix_sum[i] = prefix_sum[i - 1] + sum_brightness[i];
    }

    int answer = 0;

    if (w == 0) {
        // W=0 时,窗户只能看到某一个精确位置上的星星。
        for (int i = 1; i <= max_pos; i++) {
            if (sum_brightness[i] > answer) {
                answer = sum_brightness[i];
            }
        }
    } else {
        // 题目保证 W 为奇数,此时长度为 W 的闭区间恰好覆盖连续 W 个整数位置。
        for (int right = 1; right <= max_pos; right++) {
            int left = right - w + 1;
            if (left < 1) {
                left = 1;
            }
            int current = prefix_sum[right] - prefix_sum[left - 1];
            if (current > answer) {
                answer = current;
            }
        }
    }

    cout << answer << '\n';

    return 0;
}

复杂度

设最大坐标为 MM,题目中 M105M \leqslant 10^5

  • 读入并聚合:O(N)O(N)
  • 求前缀和:O(M)O(M)
  • 枚举所有窗口:O(M)O(M)

总时间复杂度:

O(N+M)O(N + M)

空间复杂度:

O(M)O(M)

总结

这题的难点不在算法本身,而在建模转换:

  • 原题说的是“星星散落在数轴上”
  • 真正做时要先把它变成“每个位置有多少总亮度”

一旦完成这一步,题目就退化成了非常标准的定长最大区间和问题。