规矩出方圆

规矩出方圆

https://www.nowcoder.com/practice/d1aeced14cf944969f8f5ab5036f1bba

规矩出方圆

思路

在单位正方形上有一个内切圆(圆心 ,半径 )。我们放 条水平和竖直切割线(含边界),将正方形切成若干小矩形。只要一个矩形和圆的交集面积 ,就算"被染色"。目标是让被染色的小矩形总面积最小。

先想几个关键问题:

什么样的矩形会被"浪费"? 被染色的矩形分两类——完全在圆内的(不可避免,面积贡献 = 圆面积),和横跨圆边界的"边界格子"。边界格子的整块面积被计入,但实际只有一部分落在圆内。我们的目标就是让这些边界格子尽可能薄。

怎么利用对称性? 圆关于中心 有四重对称性。最优方案可以让横、纵切割线用同一组坐标,并且关于 对称。

核心转化——角度参数化。,对称切割点位于 ,其中 中的角度。这等价于把圆弧上的点投影到坐标轴。每条切割线正好穿过圆上某个角度对应的点。

最优的自洽条件。 当切割线位于 时,圆在 处的两个交点的 坐标分别是 。如果令 (角度关于 对称),那么 ,圆的交点恰好落在另一条切割线上!这意味着边界格子在角落处恰好"贴着"圆弧。

在这个对称约束下,一个格子 位于圆外(不被染色)当且仅当 。这使得"未着色面积"的表达式变得非常干净:

$$

其中 是第 条带的宽度, 是前 条带宽度之和。

解析梯度 + L-BFGS。 由于角度对称,自由变量只有 个( 为奇数时中间角度固定为 )。对每个自由角度 ,梯度可以解析推导:

$$

用 L-BFGS 算法几十次迭代就能收敛到最优解,对 也只需不到 0.1 秒。

代码

import math

def solve():
    M = int(input())
    k = (M - 1) // 2
    n_free = k // 2
    k_odd = k % 2
    pi4 = math.pi / 4
    cos_pi4 = 0.7071067811865476

    if n_free == 0:
        if k == 0:
            print("1.0000")
        elif k == 1:
            w = 0.5 * (1.0 - cos_pi4)
            print(f"{1.0 - 4.0 * w * w:.4f}")
        elif k == 2:
            pass
        else:
            print("1.0000")
        if k <= 1:
            return

    n = n_free if n_free > 0 else 1
    if k == 2:
        n = 1

    def eval_grad(params):
        sb = sorted(params)
        cos_b = [math.cos(b) for b in sb]
        sin_b = [math.sin(b) for b in sb]

        cos_a = list(cos_b)
        if k_odd:
            cos_a.append(cos_pi4)
        for i in range(n - 1, -1, -1):
            cos_a.append(sin_b[i])

        prev = 1.0
        prefix = [0.0] * (k + 1)
        widths = [0.0] * k
        for i in range(k):
            widths[i] = 0.5 * (prev - cos_a[i])
            prefix[i + 1] = prefix[i] + widths[i]
            prev = cos_a[i]

        uc = 0.0
        prev = 1.0
        for i in range(k):
            uc += widths[i] * prefix[k - i]
        val = 1.0 - 4.0 * uc

        sort_order = sorted(range(n), key=lambda i: params[i])
        grad_sorted = [0.0] * n
        for j in range(n):
            grad_sorted[j] = 4.0 * (widths[j] * cos_b[j] - widths[k-1-j] * sin_b[j])

        grad = [0.0] * n
        for i, si in enumerate(sort_order):
            grad[si] = grad_sorted[i]

        return val, grad

    params = [(i + 1) * pi4 / (n + 1) for i in range(n)]

    lo_bound = 1e-10
    hi_bound = pi4 - 1e-10

    m_lbfgs = min(10, n)
    s_list = []
    y_list = []
    rho_list = []

    val, grad = eval_grad(params)

    for iteration in range(5000):
        if math.sqrt(sum(g*g for g in grad)) < 1e-13:
            break

        q = list(grad)
        alphas_bfgs = []
        for i in range(len(s_list) - 1, -1, -1):
            alpha = rho_list[i] * sum(s_list[i][j]*q[j] for j in range(n))
            alphas_bfgs.append(alpha)
            for j in range(n):
                q[j] -= alpha * y_list[i][j]
        alphas_bfgs.reverse()

        if s_list:
            ys = sum(y_list[-1][j]*s_list[-1][j] for j in range(n))
            yy = sum(y_list[-1][j]**2 for j in range(n))
            gamma = ys / yy if yy > 0 else 1.0
        else:
            gamma = 0.01

        r = [gamma * qi for qi in q]

        for i in range(len(s_list)):
            beta = rho_list[i] * sum(y_list[i][j]*r[j] for j in range(n))
            for j in range(n):
                r[j] += s_list[i][j] * (alphas_bfgs[i] - beta)

        direction = [-ri for ri in r]

        step = 1.0
        old_val = val
        for ls in range(30):
            new_params = [max(lo_bound, min(hi_bound, params[i] + step * direction[i])) for i in range(n)]
            new_val, new_grad = eval_grad(new_params)
            if new_val < old_val - 1e-15 * abs(old_val):
                break
            step *= 0.5
        else:
            break

        s = [new_params[j] - params[j] for j in range(n)]
        y = [new_grad[j] - grad[j] for j in range(n)]
        sy = sum(s[j]*y[j] for j in range(n))

        if sy > 1e-15:
            if len(s_list) >= m_lbfgs:
                s_list.pop(0)
                y_list.pop(0)
                rho_list.pop(0)
            s_list.append(s)
            y_list.append(y)
            rho_list.append(1.0 / sy)

        params = new_params
        val = new_val
        grad = new_grad

    print(f"{val:.4f}")

solve()

复杂度分析

  • 时间复杂度:每次 L-BFGS 迭代需要 计算函数值和梯度,总迭代次数约为 级别,故总复杂度约 ,其中
  • 空间复杂度,存储角度参数、宽度数组和 L-BFGS 历史向量。
全部评论

相关推荐

焦虑中,不知道怎么办了。。。
西北上单:应该放俩项目合理一些 我是一个业务开发项目 一个AI项目和你这个写的亮点差不多
你的简历改到第几版了
点赞 评论 收藏
分享
03-27 16:40
已编辑
门头沟学院 C++
26学院本太难了,很多公司机筛就给我刷了。机会都难拿到如果是简历存在问题也欢迎拷打————————————————————分割线——————————————————————2026.3.4更新:发完贴之后,时不时投递又收到了不少的笔试/面试邀请。主要是之前投递简历出去之后基本上都是沉默状态,年后好转了不少timeline:2026.01.21&nbsp;文远知行笔试,半年多没刷算法题&nbsp;-&gt;挂&nbsp;(后续HR说春招可以重新安排笔试)2026.2.4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;小鹏汇天&nbsp;技术一面,第二周收到结果&nbsp;-&gt;挂2026.2.12&nbsp;&nbsp;&nbsp;大众Cariad代招&nbsp;技术二面&nbsp;-&gt;Offer2026.2.28&nbsp;&nbsp;&nbsp;多益网络技术面试,由于风评太差,一直在犹豫要不要接面试&nbsp;-&gt;推迟-----------分割线-----------2026.3&nbsp;月前的某一天,临时去电网报名了二批计算机岗位的笔试2026.3.6&nbsp;从上家公司实习离职,氛围最好的一家公司,leader&nbsp;说可以帮忙转正,但是流程太长,而且我们部门据说只有一个&nbsp;hc,更想要研究生,我很有可能是会被签外包公司在这里干活,就离职了。2026.3.9&nbsp;入职新公司,大众Cariad&nbsp;以外部公司的身份进组,项目组签了三年,后续三年应该都可以在这里呆,不知道有没有希望原地跳槽。2026.3.10&nbsp;电网考试居然说我通过资格审查了,短信约我去参加资格审查,请假一天,买了&nbsp;12&nbsp;号晚上的机票回成都2026.3.15&nbsp;参加国家电网计算机类笔试2026.3.17&nbsp;电网出成绩了,感觉很低。觉得已经🈚️了2026.3.18&nbsp;收到电网面试通知,通知&nbsp;3.22-3.25&nbsp;这个时间去面试,我的岗位只招&nbsp;1&nbsp;个人。据说面试只有&nbsp;2-3&nbsp;人,不知道能不能成功----------分割线-----------2026.3.21&nbsp;电网面试结束,感觉回答的还勉勉强强,大概是2个岗位分别招1个人,一共11人面试,实际来了9人2026.3.27&nbsp;出面试成绩,满分100分,早上10:20左右发现面试成绩46,我震惊了,没截图,后面过了十分钟重新看发现面试成绩给我改成58了。但同样震惊。朋友问我是不是把面试官打了,哈哈
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务