【C++题解】最多共线点问题:小飞拿蛋糕游戏的最优策略

题目描述

小飞和哥哥在玩游戏,哥哥在平面坐标系中某些点放了蛋糕,每个点放一块。哥哥让小飞先拿蛋糕,但只能拿同一条直线上的蛋糕。小飞想尽量多拿,请你帮帮他。

输入格式:

  • 第一行一个整数 n (1 ≤ n ≤ 500)
  • 接下来 n 行,每行两个整数 x, y,表示一个点的坐标
  • 保证没有重复的点

输出格式:

  • 一个整数,表示一条直线能覆盖的最多的点数(即最多能拿到几块蛋糕)

样例输入:

复制代码
5
1 1
2 2
3 3
9 10
10 11

示意图:("*"是蛋糕,"."是没有蛋糕的点)

复制代码
* . . . . . . . . .
. * . . . . . . . .
. . * . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . *
. . . . . . . . * .

样例输出:

复制代码
3

问题分析

这是一个经典的最多共线点问题。给定平面上的 n 个点,我们需要找到一条直线,使得这条直线经过的点数最多。

关键点:

  1. n ≤ 500,时间复杂度需要控制在 O(n²) 或更低
  2. 需要考虑斜率不存在的情况(垂直线)
  3. 需要考虑重复点(题目已说明没有重复点)
  4. 需要考虑浮点数精度问题

解题思路

方法一:暴力枚举 + 斜率统计(O(n²))

对于每个点 i,计算它与其他所有点的斜率:

  1. 以点 i 为基准点
  2. 统计所有以点 i 为起点的直线的斜率
  3. 相同斜率的点在同一条直线上
  4. 记录最大点数

注意:

  • 斜率用分数形式 (dx, dy) 表示,约分后存储
  • 垂直线斜率用特殊标记(如 (1, 0))
  • 水平线斜率为 (0, 1)
  • 使用最大公约数(gcd)约分

方法二:优化考虑

  • 使用哈希表存储斜率及其出现次数
  • 时间复杂度:O(n²),对于 n=500 完全可行(500² = 250,000)

C++代码实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;

// 简化分数表示的结构体
struct Slope {
    int dx, dy;
    
    // 重载==运算符用于哈希
    bool operator==(const Slope& other) const {
        return dx == other.dx && dy == other.dy;
    }
};

// 自定义哈希函数
struct SlopeHash {
    size_t operator()(const Slope& s) const {
        return hash<long long>()((static_cast<long long>(s.dx) << 32) | s.dy);
    }
};

int maxPoints(vector<vector<int>>& points) {
    int n = points.size();
    if (n <= 2) return n;
    
    int maxCount = 1;
    
    for (int i = 0; i < n; i++) {
        unordered_map<Slope, int, SlopeHash> slopeCount;
        int duplicate = 0; // 与点i重合的点数
        int currentMax = 0;
        
        for (int j = i + 1; j < n; j++) {
            int dx = points[j][0] - points[i][0];
            int dy = points[j][1] - points[i][1];
            
            // 处理重合点
            if (dx == 0 && dy == 0) {
                duplicate++;
                continue;
            }
            
            // 约分斜率
            int g = __gcd(dx, dy);
            dx /= g;
            dy /= g;
            
            // 确保斜率表示一致(统一符号)
            if (dx < 0) {
                dx = -dx;
                dy = -dy;
            } else if (dx == 0) {
                dy = abs(dy);
            }
            
            Slope slope{dx, dy};
            slopeCount[slope]++;
            currentMax = max(currentMax, slopeCount[slope]);
        }
        
        // 当前点的最大共线点数 = 相同斜率的最大点数 + 重合点数 + 1(自身)
        maxCount = max(maxCount, currentMax + duplicate + 1);
    }
    
    return maxCount;
}

int main() {
    int n;
    cin >> n;
    
    vector<vector<int>> points(n, vector<int>(2));
    for (int i = 0; i < n; i++) {
        cin >> points[i][0] >> points[i][1];
    }
    
    cout << maxPoints(points) << endl;
    
    return 0;
}

代码详解

1. 数据结构设计

cpp 复制代码
struct Slope {
    int dx, dy;  // 斜率用分数形式表示
};

用分数 (dx, dy) 表示斜率,避免浮点数精度问题。

2. 斜率约分

cpp 复制代码
int g = __gcd(dx, dy);
dx /= g;
dy /= g;

使用最大公约数约分,确保相同斜率的表示一致。

3. 符号统一

cpp 复制代码
if (dx < 0) {
    dx = -dx;
    dy = -dy;
} else if (dx == 0) {
    dy = abs(dy);
}

确保斜率表示一致,例如 (1, 2) 和 (-1, -2) 被视为相同斜率。

4. 处理特殊情况

  • 垂直线:dx = 0,dy = 1(统一表示)
  • 水平线:dx = 1,dy = 0
  • 重合点:单独计数

算法复杂度分析

  • 时间复杂度:O(n²)

    • 外层循环:O(n)
    • 内层循环:O(n)
    • 哈希操作:O(1)
    • 总复杂度:O(n²)
  • 空间复杂度:O(n)

    • 哈希表最多存储 n-1 个斜率

对于 n = 500,最坏情况操作次数约为 250,000,完全在 1 秒时间限制内。

测试样例验证

样例1

复制代码
输入:
5
1 1
2 2
3 3
9 10
10 11

输出:3

解释:点 (1,1)、(2,2)、(3,3) 共线,斜率为 1。

样例2

复制代码
输入:
4
0 0
1 1
2 2
3 3

输出:4

解释:所有点都在直线 y = x 上。

样例3

复制代码
输入:
3
0 0
0 1
0 2

输出:3

解释:所有点都在垂直线 x = 0 上。

常见错误与注意事项

  1. 浮点数精度问题

    • 不要用浮点数计算斜率
    • 使用分数形式 (dx, dy) 表示
  2. 重复点处理

    • 题目说没有重复点,但代码中仍做了处理
    • 实际比赛中可能有重复点测试数据
  3. 边界情况

    • n = 1 时,输出应为 1
    • n = 2 时,输出应为 2
    • 所有点都相同时,输出应为 n
  4. 哈希表性能

    • 使用自定义哈希函数提高性能
    • 确保哈希冲突较少

优化思路

进一步优化

  1. 提前终止:如果剩余点数 + 当前最大点数 ≤ 已找到的最大点数,可以提前终止
  2. 并行计算:对于大数据量,可以考虑并行处理
  3. 空间优化:使用数组代替哈希表(需要处理斜率映射)

更优算法

对于 n > 1000 的情况,可以考虑更优的算法,如:

  • 随机算法(随机选取直线)
  • 分治算法
  • 扫描线算法

总结

本题是一个经典的几何问题,考察了:

  1. 哈希表的使用
  2. 分数表示斜率避免精度问题
  3. 最大公约数的应用
  4. 边界情况的处理

关键点在于用分数 (dx, dy) 表示斜率,并通过约分和符号统一来确保相同斜率的表示一致。对于 n ≤ 500 的数据范围,O(n²) 的算法完全足够。

扩展练习

  1. LeetCode 149. Max Points on a Line - 相同问题
  2. 判断三点是否共线 - 简化版本
  3. 求所有共线点对 - 统计所有共线关系
  4. 最大共线点集(允许误差) - 加入浮点数误差容忍

掌握这个问题的解法,对于理解计算几何和哈希表应用非常有帮助。

相关推荐
東隅已逝,桑榆非晚1 小时前
类和对象(中)
c++·笔记
wuminyu2 小时前
JDK21解决虚拟线程IO阻塞原理剖析
java·linux·c语言·jvm·c++
MC皮蛋侠客2 小时前
TDengine C++ 系列(4):时序查询——窗口聚合、插值、连接与时序函数
大数据·c++·tdengine
junuo2 小时前
10_C++内存分区与动态内存管理
c++·编程语言
欧特克_Glodon2 小时前
OpenCV计算机视觉开发入门与实践<二>:图像噪声与图像处理
c++·图像处理·opencv·计算机视觉
键盘会跳舞3 小时前
C++:函数对象与 std::function 源码级深度拆解——泛型算法的策略内核与可调用对象统一封装
c++·算法·仿函数
Augustzero3 小时前
为什么高性能调度器都在“偷任务”?从无锁队列看懂工作窃取
c++·后端
(Charon)3 小时前
【C++】线程安全队列(三):MPSC无锁队列、atomic与链表节点实现
c++·安全·链表
(Charon)3 小时前
【C++】:使用 mutex + deque 实现一个简单的 LockedQueue
开发语言·c++