1 条题解
-
0
魔法元首的最大亮度选择 题解
#include <bits/stdc++.h> #define double long double using std::max; const double pi = acos(-1), eps = 1e-10; struct P { double x, y; P(double _x = 0, double _y = 0): x(_x), y(_y) {} P operator-(P rhs) { return P(x - rhs.x, y - rhs.y); } P operator+(P rhs) { return P(rhs.x + x, rhs.y + y); } P operator*(double k) { return P(x * k, y * k); } double len() { return sqrt(x * x + y * y); } double ang() { double ans; if (x > 0 && y >= 0) ans = atan2(y, x); else if (x <= 0 && y > 0) ans = -atan2(y, -x) + pi; else if (x < 0 && y <= 0) ans = atan2(-y, -x) + pi; else ans = -atan2(-y, x) + pi * 2; return ans; } } x, y; int dcmp(double x) { return fabs(x) < eps ? 0 : (x > 0 ? 1 : -1); } int T, a1, a2, r1, r2, be1, be2, i; double u, v, ans; double tor(double d) { d = pi / 2 - d / 360 * 2 * pi; for (; d < 0; d += 2 * pi) ; return d; } P o(0, 0), xx(1, 0); P geta(double x, P a, P b) { x = tor(x); P v = b - a; double l, r, m, p, aa, ax, ap; aa = a.ang(); ax = aa - x; if (ax < 0) ax += 2 * pi; for (l = 0, r = 1; fabs(r - l) > 1e-14;) { m = (l + r) / 2; p = (a + v * m).ang(); ap = aa - p; if (ap < 0) ap += 2 * pi; if (ap < ax) l = m; else r = m; } if (dcmp((a + v * m).ang() - x) == 0) return a + v * m; aa = b.ang(); ax = aa - x; if (ax < 0) ax += 2 * pi; v = a - b; for (l = 0, r = 1; fabs(r - l) > 1e-14;) { m = (l + r) / 2; p = (b + v * m).ang(); ap = aa - p; if (ap < 0) ap += 2 * pi; if (ap < ax) l = m; else r = m; } if (dcmp((b + v * m).ang() - x) == 0) return b + v * m; else return P(-100, -100); } double calcli(double r, double g, double b) { return 0.3 * r + 0.59 * g + 0.11 * b; } double getli(P x) { double a = x.ang(), r = x.len(); a = 360 * (pi / 2 - a) / (2 * pi); for (; a < 0; a += 360); int h = a / 60; double f = a / 60 - h, p = 1 - r, q = 1 - f * r, t = 1 - (1 - f) * r; if (h == 0) return calcli(1, t, p); if (h == 1) return calcli(q, 1, p); if (h == 2) return calcli(p, 1, t); if (h == 3) return calcli(p, q, 1); if (h == 4) return calcli(t, p, 1); return calcli(1, p, q); } double calc(P a, P b) { P v = b - a; double l, r, m1, m2; for (l = 0, r = 1; fabs(r - l) > 1e-14;) { m1 = l + (r - l) / 3; m2 = r - (r - l) / 3; if (getli(a + v * m1) < getli(a + v * m2)) l = m1; else r = m2; } return getli(a + v * l); } int main() { scanf("%d", &T); while (T--) { ans = 0; scanf("%d%d%d%d", &a1, &r1, &a2, &r2); be1 = a1 / 60, be2 = a2 / 60; u = tor(a1), v = tor(a2); x = P(cos(u) * r1 / 100, sin(u) * r1 / 100); y = P(cos(v) * r2 / 100, sin(v) * r2 / 100); ans = calc(x, y); for (i = 0; i < 6; ++i) { P p1 = geta(i * 60, x, y); P p2 = geta(i == 5 ? 0 : (i + 1) * 60, x, y); if (p1.x != -100 && p2.x != -100) { ans = max(ans, calc(p1, p2)); } if (p1.x != -100) { ans = max(ans, calc(x, p1)); ans = max(ans, calc(y, p1)); } if (p2.x != -100) { ans = max(ans, calc(x, p2)); ans = max(ans, calc(y, p2)); } } //int z=(ans+eps)*10000; //printf("%d.%d\n",z/10000,z%10000+(ans*10000-z>=0.5)); //写了真四舍五入挂成10分,没办法啊 printf("%.4Lf\n", ans); } return 0; }算法标签
几何计算、黄金分割搜索、分段函数处理、枚举法、极坐标与直角坐标转换
题目分析
核心问题转化
题目本质是在色盘平面的直线段上寻找亮度最大的点,关键在于将色盘坐标、直线段、亮度计算转化为可求解的数学与几何问题:
- 色盘坐标映射:色盘上的 对应平面直角坐标 (极坐标转直角坐标,半径为 ,极角经角度转换得到)。
- 直线段表示:输入的两个端点坐标转换为直角坐标后,线段可参数化为 ()。
- 亮度函数特性:亮度 是 的线性组合,而 由 和 经分段函数计算得到,因此 是分段非线性函数,无解析极值公式,需通过数值优化求解。
关键约束与难点
- 分段性: 每跨越 ( 取值变化), 的计算逻辑改变,亮度函数的表达式也随之变化。
- 非线性:即使在同一 区间内, 也是 和 的非线性函数,无法直接求导找极值。
- 精度要求:输出需保留4位小数,搜索精度需达到 级别以确保结果准确。
核心思路
- 坐标统一转换:将色盘的 坐标转为平面直角坐标,简化直线段的表示与交点计算。
- 临界角度枚举:枚举 的分界角度($0^\circ、60^\circ、120^\circ、180^\circ、240^\circ、300^\circ$),找到这些角度与直线段的交点,将线段分割为多个子线段。每个子线段内的点属于同一 区间,亮度函数形式统一。
- 黄金分割搜索:对每个子线段,使用黄金分割搜索(单峰函数高效数值优化方法)寻找亮度最大值。该方法无需导数,仅通过函数值比较即可逼近极值,适合非线性、无解析表达式的函数。
- 全局最大值筛选:综合所有子线段的搜索结果、线段端点、临界角度交点的亮度,取最大值作为答案。
算法步骤
步骤1:坐标转换(色盘→直角坐标)
将输入的 和 转为平面直角坐标 :
- 角度转换:通过
tor函数将色盘的顺时针 转为数学极坐标的逆时针极角(以x轴正方向为起点)。 - 极坐标转直角坐标:,( 是归一化半径)。
步骤2:枚举临界角度,分割线段
枚举6个临界角度(,):
- 用
geta函数寻找临界角度对应的直线与线段 的交点(若存在)。 - 收集所有有效交点与线段端点,按参数 (线段参数化后的系数)排序,将原线段分割为若干子线段(每个子线段内 恒定)。
步骤3:黄金分割搜索子线段最大值
对每个子线段,通过
calc函数执行黄金分割搜索:- 子线段参数化:设子线段的起点为 、终点为 ,则子线段上的点可表示为 ()。
- 黄金分割迭代:
- 取区间内两个黄金分割点 、。
- 计算两点的亮度,保留亮度更大的点所在的子区间。
- 重复迭代直到区间长度小于 ,此时区间内任意点的亮度可视为该子线段的最大值。
步骤4:计算亮度函数
通过
getli函数计算任意直角坐标点的亮度:- 直角坐标转极角与半径:极角转为色盘的 ,半径 (还原为百分比)。
- 分段计算 :根据 确定计算公式,计算 后得到 。
- 计算亮度:。
步骤5:筛选全局最大值
比较所有子线段的搜索结果、交点亮度、端点亮度,取最大值作为最终答案,按要求保留4位小数输出。
代码解析
核心数据结构与工具函数
1. 点结构体
P- 存储直角坐标 ,重载加减、数乘运算,提供计算长度(
len)和极角(ang)的方法。 ang函数:计算点相对于原点的极角(适配不同象限的角度转换)。
2. 角度转换
tor- 功能:将色盘的 转为数学极角(逆时针从x轴正方向出发)。
- 逻辑:(修正色盘的角度起点和方向),确保角度在 范围内。
3. 交点查找
geta- 功能:寻找临界角度对应的直线与线段 的交点。
- 逻辑:通过二分法搜索线段参数 ,判断对应的点是否满足极角等于临界角度,找到则返回该点,否则返回无效点 。
4. 黄金分割搜索
calc- 功能:在子线段上搜索亮度最大值。
- 优势:收敛速度快(线性收敛,收敛率约0.618),无需导数,适合非线性函数的数值优化。
5. 亮度计算
getli- 功能:实现从直角坐标到亮度的完整转换逻辑,严格遵循题目给定的分段公式。
主函数逻辑
int main() { scanf("%d", &T); while (T--) { ans = 0; scanf("%d%d%d%d", &a1, &r1, &a2, &r2); // 1. 转换输入端点为直角坐标 u = tor(a1), v = tor(a2); x = P(cos(u) * r1 / 100, sin(u) * r1 / 100); y = P(cos(v) * r2 / 100, sin(v) * r2 / 100); // 2. 搜索原线段的亮度最大值 ans = calc(x, y); // 3. 枚举6个临界角度,分割线段并搜索 for (i = 0; i < 6; ++i) { P p1 = geta(i * 60, x, y); // 临界角度i*60°与线段的交点 P p2 = geta(i == 5 ? 0 : (i + 1) * 60, x, y); // 下一个临界角度 // 搜索交点之间的子线段 if (p1.x != -100 && p2.x != -100) ans = max(ans, calc(p1, p2)); // 搜索端点与交点之间的子线段 if (p1.x != -100) { ans = max(ans, calc(x, p1)); ans = max(ans, calc(y, p1)); } if (p2.x != -100) { ans = max(ans, calc(x, p2)); ans = max(ans, calc(y, p2)); } } // 输出结果(保留4位小数) printf("%.4Lf\n", ans); } return 0; }复杂度分析
-
时间复杂度:
- :测试数据组数()。
- :临界角度分割后的子线段数(最多7个,常数级)。
- :黄金分割搜索的迭代次数(,迭代约50次,常数级)。
- 整体为 ,效率极高,可处理所有数据规模。
-
空间复杂度: 仅使用常数个变量存储点坐标、参数和中间结果,无额外空间开销。
交付物提议
要不要我帮你生成一份亮度函数可视化脚本,支持输入线段端点,绘制线段上亮度的变化曲线,直观验证最大值位置,方便调试和理解?
- 1
信息
- ID
- 5372
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者