扫描线|离散化|seg+二分|卡常

lc3454

只统计一次_预处理: 扫描线+线段树计算正方形总面积

二分的扫描线遍历找到分割线位置,使分割线两侧面积趋近相等,最终求解分割线的纵坐标值

卡常代码: 常数时间过大导致超时

可用静态数组替代动态容器、去掉懒更新减少内存分配/函数调用等额外开销,降低运行常数以通过时间限制

constexpr int MAX_N = 100'000 + 5;

array<int, 4> y_pos[MAX_N];

int x_pos[MAX_N];

int x_diff[MAX_N];

int total_length[MAX_N << 2];

int cover_length[MAX_N << 2];

int cover_times[MAX_N << 2];

void up(int i) {

if (cover_times[i]) {

cover_length[i] = total_length[i];

} else {

cover_length[i] = cover_length[i * 2] + cover_length[i * 2 + 1];

}

}

void build(const int *arr, int i, int l, int r) {

cover_length[i] = cover_times[i] = 0;

if (l == r) {

total_length[i] = arr[l];

return;

}

int m = (l + r) / 2;

build(arr, i * 2, l, m);

build(arr, i * 2 + 1, m + 1, r);

total_length[i] = total_length[i * 2] + total_length[i * 2 + 1];

}

void update(int i, int l, int r, int ql, int qr, int v) {

if (ql <= l && r <= qr) {

cover_times[i] += v;

if (l != r) {

up(i);

} else {

cover_length[i] = cover_times[i] ? total_length[i] : 0;

}

return;

}

int m = (l + r) / 2;

if (ql <= m) {

update(i * 2, l, m, ql, qr, v);

}

if (qr > m) {

update(i * 2 + 1, m + 1, r, ql, qr, v);

}

up(i);

}

class Solution {

public:

double separateSquares(vector<vector<int>>& squares) {

int n = squares.size();

for (int i = 0; i < n; ++i) {

auto &sq = squares[i];

int x1 = sq[0], y1 = sq[1], l = sq[2];

int x2 = x1 + l, y2 = y1 + l;

y_pos[i] = {y1, x1, x2, 1};

y_pos[i + n] = {y2, x1, x2, -1};

x_pos[i] = x1;

x_pos[i + n] = x2;

}

n <<= 1;

++//离散化++

++sort(y_pos, y_pos + n, [](const auto &x, const auto &y) {return x[0] < y[0];});++

sort(x_pos, x_pos + n);

int x_cnt = unique(x_pos, x_pos + n) - x_pos - 1;

for (int i = 0; i < x_cnt; ++i)

x_diff[i] = x_pos[i + 1] - x_pos[i];

++build(x_diff, 1, 0, x_cnt - 1);++

long long total_s = 0;

for (int i = 0, pre_y = 0; i < n; ) {

int y = y_pos[i][0];

total_s += 1LL * (y - pre_y) * cover_length[1];

for (; i < n && y_pos[i][0] == y; ++i) {

auto [_, x1, x2, d] = y_pos[i];

int ql = lower_bound(x_pos, x_pos + x_cnt + 1, x1) - x_pos;

int qr = lower_bound(x_pos, x_pos + x_cnt + 1, x2) - x_pos - 1;

++update(1, 0, x_cnt - 1, ql, qr, d);++

}

pre_y = y;

}

++build(x_diff, 1, 0, x_cnt - 1);++

long long s = 0;

int pre_y = 0;

for (int i = 0; i < n; ) {

int y = y_pos[i][0];

s += 1LL * (y - pre_y) * cover_length[1];

if (s * 2 >= total_s) {

s -= 1LL * (y - pre_y) * cover_length[1];

break;

}

for (; i < n && y_pos[i][0] == y; ++i) {

auto [_, x1, x2, d] = y_pos[i];

int ql = lower_bound(x_pos, x_pos + x_cnt + 1, x1) - x_pos;

int qr = lower_bound(x_pos, x_pos + x_cnt + 1, x2) - x_pos - 1;

++update(1, 0, x_cnt - 1, ql, qr, d);++

}

pre_y = y;

}

++double ans = pre_y + (double) (total_s - 2 * s) / (cover_length[1] * 2);++

return ans;

}

};

扫描线模板(矩形面积并)

线段树+二分

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 2010;

// 边的事件结构体:存储扫描线的入边/出边信息

struct Edge {

ll x, y1, y2;

int k; // ++入边k=1(覆盖+1),出边k=-1(覆盖-1)++

Edge() {}

Edge(ll x, ll y1, ll y2, int k) : x(x), y1(y1), y2(y2), k(k) {}

// 事件点排序规则:++按x坐标升序++ ,x相同则入边优先(保证先加后减,避免漏算)

bool operator<(const Edge& t) const {

return x < t.x;

}

};

Edge e[N << 1];

ll y[N << 1]; // 存储所有y坐标,用于离散化

int cnt;

// 线段树:维护当前扫描线覆盖的y轴区间有效长度

struct Node {

int l, r;

int cnt; // 区间被覆盖的次数

ll len; // 区间的有效长度(被覆盖时的长度)

} tr[N << 3];

void build(int u, int l, int r) {

tr[u] = {l, r, 0, 0};

if (l == r) return;

int mid = l + r >> 1;

build(u << 1, l, mid);

build(u << 1 | 1, mid + 1, r);

}

// 向上更新:根据子节点和当前覆盖次数计算有效长度

void pushup(int u) {

if (tr[u].cnt) tr[u].len = y[tr[u].r + 1] - y[tr[u].l];

else if (tr[u].l == tr[u].r) tr[u].len = 0;

else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;

}

// 线段树区间更新:修改覆盖次数

void update(int u, int l, int r, int k) {

if (tr[u].l >= l && tr[u].r <= r) {

tr[u].cnt += k;

++pushup(u);++

return;

}

int mid = tr[u].l + r >> 1;

if (l <= mid) update(u << 1, l, r, k);

if (r > mid) update(u << 1 | 1, l, r, k);

++pushup(u);++

}

// 计算n个矩形的面积并

ll rectangleArea(vector<vector<ll>>& rects) {

cnt = 0;

int n = rects.size();

for (auto& rect : rects) {

ll x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3];

// 1. 构建事件点:左边界是入边,右边界是出边

e[cnt++] = Edge(x1, y1, y2, 1);

e[cnt++] = Edge(x2, y1, y2, -1);

// 收集所有y坐标,用于后续离散化

y[cnt - 2] = y1;

y[cnt - 1] = y2;

}

// ========== 离散化代码 start

sort(y, y + cnt); // 排序y坐标

// 去重:得到离散化后的唯一y坐标数量

int m = unique(y, y + cnt) - y;

// ========== 离散化代码 end

sort(e, e + cnt); // 按x坐标升序排序事件点

// ========== 事件点排序

build(1, 0, m - 2); // 线段树的叶子节点对应y[i]到y[i+1]的区间

ll res = 0;

for (int i = 0; i < cnt; i++) {

// 相邻事件点的x差 × 当前有效长度 = 这一段的面积

if (i > 0) res += tr[1].len * (e[i].x - e[i - 1].x);

// 二分查找y1,y2对应的离散化下标

++int l = lower_bound(y, y + m, e[i].y1) - y;++

int r = lower_bound(y, y + m, e[i].y2) - y;

// 更新线段树:覆盖区间[l, r-1]

update(1, l, r - 1, e[i].k);

}

return res;

}

说明

  1. 离散化作用:把++大范围的 y 坐标映射到小范围的下标++ ,避免线段树空间爆炸,是处理大坐标的必备操作

  2. 事件点排序核心作用:保证扫描线从左到右依次处理,入边先于出边的规则能避免同一 x 位置先减后加导致的有效长度计算错误

相关推荐
不穿格子的程序员2 小时前
从零开始写算法——二叉树篇6:二叉树的右视图 + 二叉树展开为链表
java·算法·链表
大志若愚YYZ2 小时前
ROS2学习 C++中的this指针
c++·学习·算法
AI科技星2 小时前
光子的几何起源与量子本质:一个源于时空本底运动的统一模型
服务器·人工智能·线性代数·算法·机器学习
源代码•宸2 小时前
Golang原理剖析(map面试与分析)
开发语言·后端·算法·面试·职场和发展·golang·map
CodeByV2 小时前
【算法题】栈
算法
sprintzer2 小时前
1.6-1.15力扣数学刷题
算法·leetcode·职场和发展
jiang_bluetooth2 小时前
channel sounding基于探测序列的时延和相位差算法
算法·蓝牙测距·channel sound·gfsk·蓝牙6.0
地平线开发者3 小时前
征程 6 算法工具链 | PTQ 深度使用指南
算法·自动驾驶
Xの哲學3 小时前
Linux 软中断深度剖析: 从设计思想到实战调试
linux·网络·算法·架构·边缘计算