UVa 10251 Min-Max Cake

题目描述

Motashota\texttt{Motashota}Motashota 是个很胖的男孩,他的弟弟 Sumit\texttt{Sumit}Sumit 很瘦。Sumit\texttt{Sumit}Sumit 只要求平等分享蛋糕上的奶油花,而 Motashota\texttt{Motashota}Motashota 不喜欢奶油花,所以不介意分享。给定蛋糕的描述和奶油花中心的位置,你需要确定 Sumit\texttt{Sumit}Sumit 必须获得的最小蛋糕体积(不包括花),以便获得他应得的平等份额的奶油花。

关键假设

  • 切割路径始终是一条直线
  • 蛋糕为圆形或方形
  • 切割平面始终垂直
  • 花始终严格在蛋糕内部
  • 蛋糕高度均匀
  • 花对称且位于蛋糕顶部平面

输入格式

第一行是整数 NNN,表示测试用例数。接下来的 NNN 行每行包含:

  • 圆形蛋糕:三个整数 LLL(半径)、HHH(高度)、DDD(花中心到蛋糕中心的距离,D<LD < LD<L
  • 方形蛋糕:四个整数 LLL(边长)、HHH(高度)、DXDXDX(水平距离,DX<L/2.0DX < L/2.0DX<L/2.0)、DYDYDY(垂直距离,DY<L/2.0DY < L/2.0DY<L/2.0

输出格式

对于每个测试用例,输出 Sumit\texttt{Sumit}Sumit 必须获得的最小蛋糕体积,保留三位小数。

题目分析

问题本质

我们需要找到一条通过花中心的直线,将蛋糕分成两部分,使得较小部分的面积最小。由于蛋糕高度均匀,体积问题可以简化为面积问题(体积 === 面积 ××× 高度)。

圆形蛋糕解法

对于圆形蛋糕,数学分析表明:

  • 当切割线垂直于圆心到花中心的连线时,较小部分的面积达到最小值
  • 较小部分的面积可以通过扇形面积减去三角形面积计算:

设圆的半径为 RRR,花中心到圆心的距离为 DDD,则最小面积为:
Amin=R2⋅arccos⁡(DR)−D⋅R2−D2A_{\text{min}} = R^2 \cdot \arccos\left(\frac{D}{R}\right) - D \cdot \sqrt{R^2 - D^2}Amin=R2arccos(RD)DR2D2

特殊情况:当 D=0D = 0D=0(花在圆心)时,任何通过圆心的直线都会将圆平分成两个半圆,面积为 πR22\frac{\pi R^2}{2}2πR2

方形蛋糕解法

对于方形蛋糕,关键观察是:

  • 极值出现在分割线连接顶点和花中心垂直于顶点-花中心连线
  • 对于每个顶点,我们考虑两种情况:
    1. 连接顶点和花中心的直线作为分割线
    2. 过花中心且垂直于顶点−-花中心连线的直线作为分割线

数学原理

  • 如果分割线不经过这些关键方向,我们可以微调分割线来获得更小的面积
  • 因此只需要检查有限的关键方向(444 个顶点 ××× 222 种情况 === 888 种情况)

面积计算

  • 使用半平面交算法计算分割线一侧的多边形面积
  • 另一侧面积 === 总面积 −- 已计算面积
  • 取所有情况中的最小面积

算法实现

核心数据结构

  • Point:表示二维点,支持向量运算
  • 主要操作:点积、叉积、旋转、长度计算

关键函数

  1. minCircleArea:计算圆形蛋糕的最小切割面积
  2. polygonArea:计算多边形面积(鞋带公式)
  3. halfPlaneIntersection:半平面交,求直线与多边形的交集
  4. minSquareArea:计算方形蛋糕的最小切割面积

复杂度分析

  • 圆形蛋糕O(1)O(1)O(1),直接公式计算
  • 方形蛋糕O(1)O(1)O(1),固定检查 888 种情况
  • 总体复杂度:O(N)O(N)O(N),其中 NNN 为测试用例数

代码实现

// Min-Max Cake
// UVa ID: 10251
// Verdict: Accepted
// Submission Date: 2025-11-28
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

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

const double PI = acos(-1.0);
const double EPS = 1e-12;

// 计算圆形蛋糕的最小切割面积
double minCircleArea(double radius, double distance) {
    if (distance < EPS) return PI * radius * radius / 2.0;
    double area = radius * radius * acos(distance / radius) - distance * sqrt(radius * radius - distance * distance);
    return area;
}

// 点结构体
struct Point {
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) {}
    Point operator+(const Point& p) const { return Point(x + p.x, y + p.y); }
    Point operator-(const Point& p) const { return Point(x - p.x, y - p.y); }
    Point operator*(double k) const { return Point(x * k, y * k); }
    double cross(const Point& p) const { return x * p.y - y * p.x; }
    double dot(const Point& p) const { return x * p.x + y * p.y; }
    double len() const { return sqrt(x * x + y * y); }
    Point rotate90() const { return Point(-y, x); } // 逆时针旋转90度
};

// 计算多边形面积(鞋带公式)
double polygonArea(const vector<Point>& poly) {
    double area = 0;
    int n = poly.size();
    for (int i = 0; i < n; i++) {
        int j = (i + 1) % n;
        area += poly[i].x * poly[j].y - poly[j].x * poly[i].y;
    }
    return fabs(area) / 2.0;
}

// 半平面交:求直线ab与多边形poly的交集(在直线ab的左侧)
vector<Point> halfPlaneIntersection(const vector<Point>& poly, const Point& a, const Point& b) {
    vector<Point> result;
    int n = poly.size();
    
    for (int i = 0; i < n; i++) {
        Point p1 = poly[i];
        Point p2 = poly[(i + 1) % n];
        
        // 判断点p1在直线的哪一侧
        double cross1 = (b - a).cross(p1 - a);
        double cross2 = (b - a).cross(p2 - a);
        
        // 如果p1在左侧,加入结果
        if (cross1 >= -EPS) result.push_back(p1);
        
        // 如果线段与直线相交,加入交点
        if (cross1 * cross2 < -EPS) {
            double t = (b - a).cross(a - p1) / (b - a).cross(p2 - p1);
            Point inter = p1 + (p2 - p1) * t;
            result.push_back(inter);
        }
    }
    
    return result;
}

// 计算方形蛋糕的最小切割面积
double minSquareArea(double side, double dx, double dy) {
    double half = side / 2.0;
    double totalArea = side * side;
    
    // 方形的四个顶点(逆时针顺序)
    vector<Point> square = {
        Point(-half, -half), Point(-half, half), 
        Point(half, half), Point(half, -half)
    };
    
    // 花的位置
    Point flower(dx, dy);
    
    double minArea = totalArea;
    
    // 考虑四个顶点与花位置的连线
    for (int i = 0; i < 4; i++) {
        Point vertex = square[i];
        
        // 情况1:连接花位置和顶点的直线作为分割线
        vector<Point> part1 = halfPlaneIntersection(square, flower, vertex);
        double area1 = polygonArea(part1);
        double area2 = totalArea - area1; // 另一部分的面积
        minArea = min(minArea, min(area1, area2));
        
        // 情况2:过花位置作该直线的垂线作为分割线
        Point dir = vertex - flower;
        Point normal = dir.rotate90(); // 垂直方向
        
        vector<Point> part3 = halfPlaneIntersection(square, flower, flower + normal);
        double area3 = polygonArea(part3);
        double area4 = totalArea - area3; // 另一部分的面积
        minArea = min(minArea, min(area3, area4));
    }
    
    return minArea;
}

int main() {
    int n;
    cin >> n;
    
    cout << fixed << setprecision(3);
    
    for (int i = 0; i < n; i++) {
        int first, second, third, fourth = 0;
        cin >> first >> second >> third;
        
        char c = cin.peek();
        if (c != '\n' && c != EOF) {
            cin >> fourth;
            // 方形蛋糕
            double area = minSquareArea(first, third, fourth);
            double volume = area * second;
            cout << volume << endl;
        } else {
            // 圆形蛋糕
            double area = minCircleArea(first, third);
            double volume = area * second;
            cout << volume << endl;
        }
    }
    
    return 0;
}

总结

本题的关键在于理解几何极值原理:

  • 圆形蛋糕:最小面积出现在切割线垂直于圆心−-花中心连线时
  • 方形蛋糕:最小面积出现在切割线经过关键方向(顶点连线或其垂线)时

通过精确的几何计算和半平面交算法,我们可以高效地解决这个问题。算法的时间复杂度为 O(N)O(N)O(N),完全满足题目要求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值