当前位置:首页 > 技术 > 正文内容

POJ 2318 - 玩具分配问题(计算几何叉积应用)

访客 技术 2026年7月4日 1

题目概述

给定一个矩形箱子,内部有 n 条垂直隔板将箱子分成 n+1 个区域。依次给定 m 个玩具的落点坐标,要求统计每个区域内玩具的数量。

隔板的端点坐标为 (Ui, y1) 和 (Li, y2),其中 (x1, y1) 是矩形左上角,(x2, y2) 是矩形右下角。隔板之间不相交,且按从左到右的顺序给出。

核心思路

本题的本质是判断点与直线的位置关系。对于每个玩具,只需要判断它落在哪两个隔板之间即可。

利用叉积(Cross Product)可以判断点与直线的相对位置:

  • 设直线端点为 A(x1, y1) 和 B(x2, y2),待检测点为 P(x, y)
  • 计算向量 AB × AP 的叉积值
  • 若叉积 > 0,则 P 在直线 AB 的顺时针方向(左側)
  • 若叉积 < 0,则 P 在直线 AB 的逆时针方向(右側)

具体实现时,将所有隔板按上端点 x 坐标排序。从左到右遍历每个玩具,对于当前玩具,依次与各隔板进行比较。当发现玩具在某隔板的左侧时,该玩具就落入该隔板左侧对应的区域。

实现代码

方案一:基础实现

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

struct Partition {
    int upperX;  // 隔板上端点 x 坐标
    int lowerX;  // 隔板下端点 x 坐标
};

const int MAXN = 5005;
int n, m;
int boxTopY, boxBottomY, boxLeftX, boxRightX;
int result[MAXN];
Partition partitions[MAXN];

// 计算叉积:向量 (x2-x1, y2-y1) 与 (x3-x1, y3-y1) 的叉积
long long computeCross(int x1, int y1, int x2, int y2, int x3, int y3) {
    return (long long)(x2 - x1) * (y3 - y1) - (long long)(y2 - y1) * (x3 - x1);
}

// 判断点 (px, py) 是否在隔板 i 的左侧
bool isLeftOfPartition(int px, int py, const Partition& part) {
    // 构建向量:隔板上端点到下端点,以及隔板上端点到玩具点
    long long crossProduct = computeCross(
        part.upperX, boxTopY,    // 隔板上端点 A
        part.lowerX, boxBottomY, // 隔板下端点 B  
        px, py                   // 玩具点 P
    );
    // 叉积小于 0 表示点在直线的左侧(顺时针方向)
    return crossProduct < 0;
}

// 统计单个玩具所属区域
void classifyToy(int px, int py) {
    for (int i = 0; i < n; i++) {
        if (isLeftOfPartition(px, py, partitions[i])) {
            result[i]++;  // 落入第 i 个区域(第 i 条隔板左侧)
            return;
        }
    }
    result[n]++;  // 落入最右侧区域(第 n+1 个区域)
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    while (cin >> n) {
        if (n == 0) break;
        cin >> m >> boxLeftX >> boxTopY >> boxRightX >> boxBottomY;
        
        for (int i = 0; i < n; i++) {
            cin >> partitions[i].upperX >> partitions[i].lowerX;
        }
        
        // 按上端点 x 坐标排序,确保隔板从左到右排列
        sort(partitions, partitions + n, [](const Partition& a, const Partition& b) {
            return a.upperX < b.upperX;
        });
        
        memset(result, 0, sizeof(result));
        
        for (int i = 0; i < m; i++) {
            int toyX, toyY;
            cin >> toyX >> toyY;
            classifyToy(toyX, toyY);
        }
        
        for (int i = 0; i <= n; i++) {
            cout << i << ": " << result[i] << "\n";
        }
        cout << "\n";
    }
    return 0;
}

方案二:优化实现

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

struct LineSegment {
    int topX;    // 上端点 x 坐标
    int bottomX; // 下端点 x 坐标
    
    bool operator<(const LineSegment& other) const {
        if (topX != other.topX) return topX < other.topX;
        return bottomX < other.bottomX;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    const int MAX = 5005;
    int n, m;
    int x1, y1, x2, y2;
    
    bool firstCase = true;
    while (cin >> n) {
        if (n == 0) break;
        cin >> m >> x1 >> y1 >> x2 >> y2;
        
        vector<LineSegment> lines(n);
        for (int i = 0; i < n; i++) {
            cin >> lines[i].topX >> lines[i].bottomX;
        }
        
        sort(lines.begin(), lines.end());
        
        vector<int> count(n + 1, 0);
        
        for (int i = 0; i < m; i++) {
            int px, py;
            cin >> px >> py;
            
            bool placed = false;
            for (int j = 0; j < n; j++) {
                // 使用叉积判断点与直线的位置关系
                // (lines[j].bottomX - lines[j].topX, y2 - y1) 是隔板向量
                // (px - lines[j].topX, py - y1) 是点到隔板上端点的向量
                long long cross = (long long)(lines[j].bottomX - lines[j].topX) * (py - y1) 
                                - (long long)(y2 - y1) * (px - lines[j].topX);
                
                // 叉积大于 0 表示点在隔板左侧(顺时针方向)
                if (cross > 0) {
                    count[j]++;      // 落入第 j 个区域
                    placed = true;
                    break;
                }
            }
            if (!placed) {
                count[n]++;  // 落入最右侧区域
            }
        }
        
        if (!firstCase) cout << "\n";
        firstCase = false;
        
        for (int i = 0; i <= n; i++) {
            cout << i << ": " << count[i] << "\n";
        }
    }
    return 0;
}

关键细节

需要注意以下几个要点:

  1. 坐标系方向:题目中 y1 为上边界(较小值),y2 为下边界(较大值),与常规数学坐标系相反,需要在叉积计算时特别注意
  2. 排序必要性:虽然题目说明隔板已按从左到右顺序给出,但为保险起见仍进行排序
  3. 边界处理:题目保证玩具不会落在隔板或边界上,只需处理区域内部的归属

该算法的时间复杂度为 O(n × m),空间复杂度为 O(n),完全能够满足题目约束(n, m ≤ 5000)。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。