1 条题解

  • 0
    @ 2025-11-17 21:10:08

    魔法元首的最大亮度选择 题解

    #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;
    }
    

    算法标签

    几何计算黄金分割搜索分段函数处理枚举法极坐标与直角坐标转换

    题目分析

    核心问题转化

    题目本质是在色盘平面的直线段上寻找亮度最大的点,关键在于将色盘坐标、直线段、亮度计算转化为可求解的数学与几何问题:

    1. 色盘坐标映射:色盘上的 (α,r%)(\alpha^\circ, r\%) 对应平面直角坐标 (x,y)(x, y)(极坐标转直角坐标,半径为 r/100r/100,极角经角度转换得到)。
    2. 直线段表示:输入的两个端点坐标转换为直角坐标后,线段可参数化为 P(m)=P1+m×(P2P1)P(m) = P_1 + m \times (P_2 - P_1)m[0,1]m \in [0,1])。
    3. 亮度函数特性:亮度 LLRGBR、G、B 的线性组合,而 RGBR、G、Bα\alpharr 经分段函数计算得到,因此 LL 是分段非线性函数,无解析极值公式,需通过数值优化求解。

    关键约束与难点

    • 分段性:α\alpha 每跨越 6060^\circhh 取值变化),RGBR、G、B 的计算逻辑改变,亮度函数的表达式也随之变化。
    • 非线性:即使在同一 hh 区间内,LL 也是 α\alpharr 的非线性函数,无法直接求导找极值。
    • 精度要求:输出需保留4位小数,搜索精度需达到 101410^{-14} 级别以确保结果准确。

    核心思路

    1. 坐标统一转换:将色盘的 (α,r)(\alpha, r) 坐标转为平面直角坐标,简化直线段的表示与交点计算。
    2. 临界角度枚举:枚举 hh 的分界角度($0^\circ、60^\circ、120^\circ、180^\circ、240^\circ、300^\circ$),找到这些角度与直线段的交点,将线段分割为多个子线段。每个子线段内的点属于同一 hh 区间,亮度函数形式统一。
    3. 黄金分割搜索:对每个子线段,使用黄金分割搜索(单峰函数高效数值优化方法)寻找亮度最大值。该方法无需导数,仅通过函数值比较即可逼近极值,适合非线性、无解析表达式的函数。
    4. 全局最大值筛选:综合所有子线段的搜索结果、线段端点、临界角度交点的亮度,取最大值作为答案。

    算法步骤

    步骤1:坐标转换(色盘→直角坐标)

    将输入的 (α1,r1)(\alpha_1, r_1)(α2,r2)(\alpha_2, r_2) 转为平面直角坐标 P1P2P_1、P_2

    1. 角度转换:通过 tor 函数将色盘的顺时针 α\alpha^\circ 转为数学极坐标的逆时针极角(以x轴正方向为起点)。
    2. 极坐标转直角坐标:x=cos(极角)×(r/100)x = \cos(\text{极角}) \times (r/100)y=sin(极角)×(r/100)y = \sin(\text{极角}) \times (r/100)r/100r/100 是归一化半径)。

    步骤2:枚举临界角度,分割线段

    枚举6个临界角度(i×60i \times 60^\circi=0,1,...,5i=0,1,...,5):

    1. geta 函数寻找临界角度对应的直线与线段 P1P2P_1P_2 的交点(若存在)。
    2. 收集所有有效交点与线段端点,按参数 mm(线段参数化后的系数)排序,将原线段分割为若干子线段(每个子线段内 hh 恒定)。

    步骤3:黄金分割搜索子线段最大值

    对每个子线段,通过 calc 函数执行黄金分割搜索:

    1. 子线段参数化:设子线段的起点为 AA、终点为 BB,则子线段上的点可表示为 A+k×(BA)A + k \times (B - A)k[0,1]k \in [0,1])。
    2. 黄金分割迭代:
      • 取区间内两个黄金分割点 m1=l+(rl)/3m_1 = l + (r-l)/3m2=r(rl)/3m_2 = r - (r-l)/3
      • 计算两点的亮度,保留亮度更大的点所在的子区间。
      • 重复迭代直到区间长度小于 101410^{-14},此时区间内任意点的亮度可视为该子线段的最大值。

    步骤4:计算亮度函数

    通过 getli 函数计算任意直角坐标点的亮度:

    1. 直角坐标转极角与半径:极角转为色盘的 α\alpha^\circ,半径 r=点到原点的距离×100r = \text{点到原点的距离} \times 100(还原为百分比)。
    2. 分段计算 RGBR、G、B:根据 h=α/60h = \lfloor \alpha/60 \rfloor 确定计算公式,计算 fpqtf、p、q、t 后得到 RGBR、G、B
    3. 计算亮度:L=0.3R+0.59G+0.11BL = 0.3R + 0.59G + 0.11B

    步骤5:筛选全局最大值

    比较所有子线段的搜索结果、交点亮度、端点亮度,取最大值作为最终答案,按要求保留4位小数输出。

    代码解析

    核心数据结构与工具函数

    1. 点结构体 P

    • 存储直角坐标 (x,y)(x,y),重载加减、数乘运算,提供计算长度(len)和极角(ang)的方法。
    • ang 函数:计算点相对于原点的极角(适配不同象限的角度转换)。

    2. 角度转换 tor

    • 功能:将色盘的 α\alpha^\circ 转为数学极角(逆时针从x轴正方向出发)。
    • 逻辑:极角=π/2α×2π/360\text{极角} = \pi/2 - \alpha^\circ \times 2\pi / 360(修正色盘的角度起点和方向),确保角度在 [0,2π)[0, 2\pi) 范围内。

    3. 交点查找 geta

    • 功能:寻找临界角度对应的直线与线段 P1P2P_1P_2 的交点。
    • 逻辑:通过二分法搜索线段参数 mm,判断对应的点是否满足极角等于临界角度,找到则返回该点,否则返回无效点 (100,100)(-100,-100)

    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;
    }
    

    复杂度分析

    • 时间复杂度O(T×K×log(1/ϵ))O(T \times K \times \log(1/\epsilon))

      • TT:测试数据组数(T100T \leq 100)。
      • KK:临界角度分割后的子线段数(最多7个,常数级)。
      • log(1/ϵ)\log(1/\epsilon):黄金分割搜索的迭代次数(ϵ=1014\epsilon = 10^{-14},迭代约50次,常数级)。
      • 整体为 O(T)O(T),效率极高,可处理所有数据规模。
    • 空间复杂度O(1)O(1) 仅使用常数个变量存储点坐标、参数和中间结果,无额外空间开销。

    交付物提议

    要不要我帮你生成一份亮度函数可视化脚本,支持输入线段端点,绘制线段上亮度的变化曲线,直观验证最大值位置,方便调试和理解?

    • 1

    信息

    ID
    5372
    时间
    2000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者