最小正方形覆盖

起因是做2024上海市赛的C题,题目是这样的:
image

这题的做法就是n^3枚举圆的情况(三点确定圆),剩余的点用一个最小正方形覆盖住。(还有一些corner cases,比如圆只覆盖1~2个点。注意不需要考虑0,因为覆盖一个点面积为0,一定优于0个点)
于是关键就来到了子问题:最小正方形覆盖。

这个题目,目前互联网上所有题解都认为是三分,只发现一个人用的退火,不知道他是否发现了三分的错误
image

也就是认为旋转正方形过程中,框住这些点的最小面积,是一个凸函数,但是这根本不正确。
这里给出一组hack数据,

19
-9599 -664
-230 -7409
-7676 -721
-7510 8825
-5706 7606
8025 -1377
-4980 8470
1910 6394
7237 6414
4045 3941
4769 5083
5100 8678
7678 -8787
-872 5143
-4544 2787
-8348 7831
-5726 8006
23 4415
1793 -4819
发现这个函数长这样
164510c09b6663cde731ac7a6a9e488e

所以压根不能三分。退火倒是能一定程度解决上面这组数据卡进局部最优的问题,但严格正确的方法是什么?

上海市赛此题的题解给了我们答案:取每个凸包边的平行和垂直方向,作为两两之间支撑点不变的事件向量,先考虑这些情况(一个边在正方形上),然后考虑他们之间的情况——此时两方向投影必定相等。代码如下。

double minSqArea(vector<Pt> P)
{
    P = convexHull(P);
    if(P.size() <= 1) return 0;
    if(P.size() == 2)
    {
        double d = (P[0] - P[1]).len();
        return d * d / 2.0;
    }
    int n = P.size();
    P.push_back(P[0]);
    vector<Pt> vec;
    for(int i = 0; i < n; i++)
    {
        Pt e = P[i + 1] - P[i];
        vec.push_back(e);
        vec.push_back(e.rot());
        vec.push_back(-e);
        vec.push_back((-e).rot());
    }
    sort(vec.begin(), vec.end(), polarCmp);
    int i = n - 1, j = n - 1, k = n - 1, l = n - 1;
    Pt v = vec[0];
    while(i > 0 && v.cross(P[i - 1] - P[i]) <= 0) i--;
    while(j > 0 && v.dot(P[j - 1] - P[j]) >= 0) j--;
    while(k > 0 && v.cross(P[k - 1] - P[k]) >= 0) k--;
    while(l > 0 && v.dot(P[l - 1] - P[l]) <= 0) l--;
    double ans = 1e18;
    for(int u = 0; u < vec.size(); u++)
    {
        Pt t = vec[u];
        while(t.cross(P[i + 1] - P[i]) <= 0) i = (i + 1) % n;
        while(t.dot(P[j + 1] - P[j]) >= 0) j = (j + 1) % n;
        while(t.cross(P[k + 1] - P[k]) >= 0) k = (k + 1) % n;
        while(t.dot(P[l + 1] - P[l]) <= 0) l = (l + 1) % n;
        double w = t.cross(P[k] - P[i]) / t.len();
        double h = t.dot(P[j] - P[l]) / t.len();
        double side = max(w, h);
        ans = min(ans, side * side);
        Pt a = (P[i] - P[k]).rot(), b = P[j] - P[l];
        Pt c = (b - a).rot();
        if(a.dot(c) < 0) c = -c;
        Pt t1 = (u + 1 == vec.size() ? vec[0] : vec[u + 1]);
        if(c.dot(c) > 0 && t.cross(c) >= 0 && c.cross(t1) >= 0)
        {
            double s = a.dot(c) / c.len();
            ans = min(ans, s * s);
        }
    }
    return ans;
}
posted @ 2026-10-12 21:25  Astral_Plane  阅读(10)  评论(0)    收藏  举报