test

好——把问题明确化后我们可以把它归结为一个 加权区间重叠最大值(weighted interval stabbing 的问题:

  • 每个正方形(或矩形,边平行坐标轴)记为 \((x_i,y_i,L_i,w_i)\),其中 \(w_i\) 是“该方块轮廓的权值”。
  • 黑线是形如 \(y=x+c\) 的所有平移(由参数 \(c\) 决定)。
  • 当且仅当存在一点 \((x,y)\) 满足
    \( x\in[x_i,x_i+L_i],\quad y\in[y_i,y_i+L_i],\quad y=x+c \)
    时,黑线与该方块轮廓相交(也即该方块被“命中”)。
  • 把上面不等式消去 \(x,y\) 后,可以得到关于 \(c\) 的区间:
    对第 \(i\) 个方块,黑线命中它当且仅当
    \( c \in [,a_i,; b_i,],\quad \text{其中 } a_i = y_i - x_i - L_i,; b_i = y_i + L_i - x_i. \)
    (推导:\(c=y-x\),取 \(y\) 最小、\(x\) 最大得到最小值;取 \(y\) 最大、\(x\) 最小得到最大值。)

因此问题变为:

给定 \(n\) 个实数区间 \([a_i,b_i]\),每个区间有权重 \(w_i\),求某个实数 \(c\) 使得覆盖该点 \(c\) 的区间权重和最大 —— 即求
(\(\max_c \sum_{i: c\in[a_i,b_i]} w_i\))。

这就是经典的扫描/事件点法可以在 \(O(n\log n)\)\(O(n)\)(排序 dominates)内解决的问题。


算法(思路)

  1. 对每个方块计算区间端点 (a_i,b_i)。

  2. 构造两个事件:

    • 在坐标 (a_i) 处发生“加上 (w_i)”;
    • 在坐标 (b_i) 处发生“减去 (w_i)”(为了使闭区间计数正确,事件在相同坐标时应先处理所有加事件,再处理减事件,这样当某些区间的右端等于其他区间的左端时,这一点会被两者同时计算到)。
  3. 对事件按坐标排序,坐标相同时按“加事件先于减事件”排序。

  4. 从左到右扫描事件,维护当前权重和 cur,并在每一步更新答案 ans = max(ans, cur)

  5. 最终 ans 即为答案;如果还需要对应的 c 值,可以在更新 ans 时记录当前坐标(或坐标区间)。

时间复杂度:排序 (O(n\log n)),扫描 (O(n))。内存 (O(n))。


伪代码

events = []  // 每个元素 (coord, type, weight)
for i in 1..n:
    a = y[i] - x[i] - L[i]
    b = y[i] + L[i] - x[i]
    events.append((a, +1, w[i]))   // +1 表示加权重
    events.append((b, -1, w[i]))   // -1 表示减权重

// 排序:先按 coord 升序;若 coord 相同,type = +1(加)排在 type = -1(减)前
sort(events, key = (coord, -type))

cur = 0
ans = 0
best_c = None
for ev in events:
    coord, type, w = ev
    if type == +1:
        cur += w
        if cur > ans:
            ans = cur
            best_c = coord
    else:
        // 在这里减去权值。注意若想把 b 点也视为被包含,则应先处理加事件再处理减事件(我们已经按排序保证)
        cur -= w

return ans, best_c

说明:best_c 记录的是事件坐标(若有一段区间而最大值在区间内部,则任何该段内点都可)。如果你想返回一个具体的 c(实数)且最大值在某个开区间内,返回该开区间任一点即可(例如事件坐标之后的一个小邻域内)。


C++ 实现(示例)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Event {
    long long coord;
    int type; // +1 add, -1 remove
    long long w;
};
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    if(!(cin >> n)) return 0;
    vector<Event> ev;
    for(int i=0;i<n;i++){
        long long x,y,L,w;
        cin >> x >> y >> L >> w;
        long long a = y - x - L;
        long long b = y + L - x;
        ev.push_back({a, +1, w});
        ev.push_back({b, -1, w});
    }
    sort(ev.begin(), ev.end(), [](const Event& A, const Event& B){
        if(A.coord != B.coord) return A.coord < B.coord;
        return A.type > B.type; // +1 (add) should come before -1 (remove)
    });
    long long cur = 0, ans = LLONG_MIN;
    long long bestCoord = 0;
    for(auto &e : ev){
        if(e.type == +1){
            cur += e.w;
            if(cur > ans){
                ans = cur;
                bestCoord = e.coord;
            }
        } else {
            cur -= e.w;
        }
    }
    cout << ans << "\n";
    // 若需要 bestCoord 输出,取消下行注释:
    // cout << "best c approx: " << bestCoord << "\n";
    return 0;
}

注意事项

  • 若输入坐标或边长可能为非整数(实数),用 double 并适当处理排序的相等容差(EPS);事件排序规则不变(先加后减)。
  • w_i 可以为负,也可用同样的方法(但若权值负,答案可能是 0 也可能更小,取决问题定义)。
  • 若想保证在区间端点同时被计算(即闭区间包含两端),我们在排序时把加事件放在相同 coord 时的前面,这样 c=a_i=b_j 时两个区间都会被计入。

如果你愿意,我可以:

  • 把上面的 C++ 换成 Python(方便调试);
  • 或者在你给出具体数据(坐标、边长、权值)时替你跑一个示例并给出最大权值与对应的 \(c\)
posted @ 2025-10-14 19:42  yanbinmu  阅读(7)  评论(0)    收藏  举报