光线追踪

枚举反射线段的内部整点,按水平线和竖直线有序维护最近碰撞点。

OJ: shumeng

题目 ID: CSP202206D

难度:提高+/省选-

标签:模拟扫描线有序集合几何

日期: 2026-07-31 16:21

形式化题目

平面内动态插入、删除 45 度角摆放的反射线段,每条线段有反射折损系数 aa。有若干查询:从整数点 (x,y)(x,y) 以水平或竖直方向发射强度 II 的光线,移动 tt 时刻后输出最终位置与取整后的强度;强度小于 1 时输出 0 0 0

思路

光线从整数点沿坐标轴移动,45 度线段与它的交点也必然是整数点,因此可以把问题退化到一维坐标上处理。

只维护线段内部整点

把每条 45 度线段的内部整点全部枚举出来:

  • yy 分组,维护每个水平方向上的点集;
  • xx 分组,维护每个竖直方向上的点集。

只维护内部点可以自动排除端点(题目约定反射面不含端点)。每个点记录它属于哪条线段,删除线段时只需把它的所有点移除。

用有序集合找最近碰撞点

查询时光线沿某个方向移动,从对应直线(水平线或竖直线)的有序集合里用 upper_bound / lower_bound 找运动方向上的最近点:

  • 向右找 y 固定的集合中第一个大于 xx 的点;
  • 向左找 y 固定的集合中最后一个小于 xx 的点;
  • 向上、向下同理在 x 固定的集合中查找。

反射与折损

碰撞后强度乘以该线段的折损系数 aa,方向按斜率反射:

  • 斜率 +1+1:方向映射为 010\leftrightarrow 1232\leftrightarrow 3
  • 斜率 1-1:方向映射为 030\leftrightarrow 3121\leftrightarrow 2

由于 a0.8a\le 0.8I109I\le 10^9,单次查询的碰撞次数很少;强度降到 1 以下就立即结束。

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:40
 */
#include <bits/stdc++.h>
using namespace std;

// 一个 45 度反射面,由两个整数端点确定,方向为斜率 ±1
struct Segment {
    long long x1, y1, x2, y2; // 两个端点
    double loss;              // 反射折损系数 a
    int slope;                // 斜率:1 或 -1
    vector<pair<long long, long long> > point; // 线段内部所有整点
};

int operation_count;
vector<Segment> segment;                                    // 线段表,下标对应操作编号
map<long long, set<pair<long long, int> > > horizontal;     // 按 y 分组,保存 (x, 线段编号)
map<long long, set<pair<long long, int> > > vertical;       // 按 x 分组,保存 (y, 线段编号)

// 把线段内部的所有整点插入水平线和竖直线
void insert_segment(int id) {
    Segment &now = segment[id];
    long long length = llabs(now.x2 - now.x1);  // 线段内部点的个数 = 长度 - 1
    long long dx = now.x2 > now.x1 ? 1 : -1;
    long long dy = now.y2 > now.y1 ? 1 : -1;
    for (long long i = 1; i < length; i++) {
        long long px = now.x1 + dx * i;
        long long py = now.y1 + dy * i;
        now.point.push_back({px, py});
        horizontal[py].insert({px, id});
        vertical[px].insert({py, id});
    }
}

// 删除线段时,把它维护的所有内部整点从两条线上移除
void erase_segment(int id) {
    Segment &now = segment[id];
    for (int i = 0; i < (int)now.point.size(); i++) {
        horizontal[now.point[i].second].erase({now.point[i].first, id});
        vertical[now.point[i].first].erase({now.point[i].second, id});
    }
}

// 在方向 direction 上找从 (x,y) 出发最近的碰撞点。
// direction: 0 向右, 1 向上, 2 向左, 3 向下。找到返回 true 并回传距离和线段编号。
bool find_next(long long x, long long y, int direction, long long &distance, int &id) {
    if (direction == 0) {
        map<long long, set<pair<long long, int> > >::iterator line = horizontal.find(y);
        if (line == horizontal.end()) return false;
        set<pair<long long, int> >::iterator it = line->second.upper_bound({x, INT_MAX});
        if (it == line->second.end()) return false;
        distance = it->first - x;
        id = it->second;
        return true;
    }
    if (direction == 2) {
        map<long long, set<pair<long long, int> > >::iterator line = horizontal.find(y);
        if (line == horizontal.end()) return false;
        set<pair<long long, int> >::iterator it = line->second.lower_bound({x, -1});
        if (it == line->second.begin()) return false;
        --it;
        distance = x - it->first;
        id = it->second;
        return true;
    }
    if (direction == 1) {
        map<long long, set<pair<long long, int> > >::iterator line = vertical.find(x);
        if (line == vertical.end()) return false;
        set<pair<long long, int> >::iterator it = line->second.upper_bound({y, INT_MAX});
        if (it == line->second.end()) return false;
        distance = it->first - y;
        id = it->second;
        return true;
    }
    map<long long, set<pair<long long, int> > >::iterator line = vertical.find(x);
    if (line == vertical.end()) return false;
    set<pair<long long, int> >::iterator it = line->second.lower_bound({y, -1});
    if (it == line->second.begin()) return false;
    --it;
    distance = y - it->first;
    id = it->second;
    return true;
}

// 根据斜率计算反射后的新方向
int reflected_direction(int direction, int slope) {
    if (slope == 1) {
        int change[4] = {1, 0, 3, 2}; // 斜率 +1:左右互换,上下互换
        return change[direction];
    }
    int change[4] = {3, 2, 1, 0};     // 斜率 -1:水平变反向,垂直变反向
    return change[direction];
}

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

    cin >> operation_count;
    segment.resize(operation_count + 1);
    for (int operation = 1; operation <= operation_count; operation++) {
        int type;
        cin >> type;
        if (type == 1) {
            Segment now;
            cin >> now.x1 >> now.y1 >> now.x2 >> now.y2 >> now.loss;
            now.slope = (now.x2 - now.x1) * (now.y2 - now.y1) > 0 ? 1 : -1;
            segment[operation] = now;
            insert_segment(operation);
        } else if (type == 2) {
            int deleted;
            cin >> deleted;
            erase_segment(deleted);
        } else {
            long long x, y, time;
            double intensity;
            int direction;
            cin >> x >> y >> direction >> intensity >> time;
            // 逐次找最近碰撞点,直到时间用完或强度耗尽
            while (time > 0 && intensity >= 1) {
                long long distance;
                int id;
                if (!find_next(x, y, direction, distance, id) || distance > time) {
                    // 本次剩余时间内不再有碰撞,直线走完
                    if (direction == 0) x += time;
                    if (direction == 1) y += time;
                    if (direction == 2) x -= time;
                    if (direction == 3) y -= time;
                    time = 0;
                    break;
                }
                // 移动到碰撞点,消耗对应时间
                if (direction == 0) x += distance;
                if (direction == 1) y += distance;
                if (direction == 2) x -= distance;
                if (direction == 3) y -= distance;
                time -= distance;
                intensity *= segment[id].loss;      // 反射折损
                direction = reflected_direction(direction, segment[id].slope);
            }
            if (intensity < 1) cout << "0 0 0\n";   // 已耗散
            else cout << x << ' ' << y << ' ' << (long long)floor(intensity) << '\n';
        }
    }

    return 0;
}

复杂度

设所有线段内部整点总数为 KK。插入和删除每个点都是有序集合操作,总代价为 O(KlogK)O(K\log K);单次查询每次碰撞在有序集合中找邻点,复杂度为 O(HlogK)O(H\log K),其中 HH 为碰撞次数;空间复杂度为 O(K)O(K)

总结

将水平、竖直方向分开维护后,几何碰撞问题退化为一维有序集合中的邻点查询。只维护线段内部点自动排除了端点歧义,插入、删除和查询都统一处理。