题目描述
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=R2⋅arccos(RD)−D⋅R2−D2
特殊情况:当 D=0D = 0D=0(花在圆心)时,任何通过圆心的直线都会将圆平分成两个半圆,面积为 πR22\frac{\pi R^2}{2}2πR2。
方形蛋糕解法
对于方形蛋糕,关键观察是:
- 极值出现在分割线连接顶点和花中心或垂直于顶点-花中心连线时
- 对于每个顶点,我们考虑两种情况:
- 连接顶点和花中心的直线作为分割线
- 过花中心且垂直于顶点−-−花中心连线的直线作为分割线
数学原理:
- 如果分割线不经过这些关键方向,我们可以微调分割线来获得更小的面积
- 因此只需要检查有限的关键方向(444 个顶点 ××× 222 种情况 === 888 种情况)
面积计算:
- 使用半平面交算法计算分割线一侧的多边形面积
- 另一侧面积 === 总面积 −-− 已计算面积
- 取所有情况中的最小面积
算法实现
核心数据结构
Point:表示二维点,支持向量运算- 主要操作:点积、叉积、旋转、长度计算
关键函数
minCircleArea:计算圆形蛋糕的最小切割面积polygonArea:计算多边形面积(鞋带公式)halfPlaneIntersection:半平面交,求直线与多边形的交集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),完全满足题目要求。
190

被折叠的 条评论
为什么被折叠?



