2025.8计算几何做题记录

前言

smb 给我们讲了两天计算几何,然后我写了很多题,现在已经能熟练地用一些东西了,后续估计会抽时间稍微复习一下比较重要的板子,于是今天就准备把做题记录写了。在放题之前我先把板子放上去。

平面最近点对

自适应辛普森法

二维凸包

动态维护凸包

闵可夫斯基和

旋转卡壳

半平面交

最小圆覆盖

现在就可以愉快的写题啦。

切割多边形

发现数据范围极小考虑爆搜,每次算周长的时候可以枚举所有线与当前的交点,然后因为目标图形上的点一定被保留于是取中点(因为最方便)即可。

inline void dfs(int cur, db res){
    if(cur == k)return ans = min(ans, res), void();
    for(int i = 0; i < k; ++i)if(! vs[i]){
        pnt mid = (a[i].s + a[i].t) / 2, l, r; poly f;
        for(int j = 0; j < 4; ++j)f.ins(inter(bj[j], a[i]));
        for(int j = 0; j < k; ++j)if(vs[j])f.ins(inter(a[j], a[i]));
        if(dcmp(f[0].x - f[f.p.size() - 1].x) == 0){
            sort(f.p.begin(), f.p.end(), cmpy);
            for(auto x : f.p)if(dcmp(x.y - mid.y) < 0)l = x; else {r = x; break;}
        }
        else{
            sort(f.p.begin(), f.p.end(), cmpx);
            for(auto x : f.p)if(dcmp(x.x - mid.x) < 0)l = x; else {r = x; break;}
        }
        vs[i] = true; dfs(cur + 1, res + Len(r - l)); vs[i] = false;
    }
}

signed main(){
    ios :: sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    cin >> n >> m >> k; g.rd(k); for(int i = 0; i < g.p.size(); ++i)a[i] = line(g[i], g[g.nex(i)]);
    bj[0] = line(pnt(0, 0), pnt(0, m)); bj[1] = line(pnt(0, 0), pnt(n, 0));
    bj[2] = line(pnt(0, m), pnt(n, m)); bj[3] = line(pnt(n, 0), pnt(n, m));
    cout << fixed << setprecision (3); dfs(0, 0); return cout << ans, 0;
}

最大土地面积

没啥好讲的,就是魔改一下旋转卡壳板子即可。

inline void solve(){
    int m = b.p.size();
    if(m < 3)return cout << "0.000", void();
    cout << fixed << setprecision (3);
    if(m < 4){
        db ans = 0;
        for(int i = 0; i < n; ++i)if(chk(a[i])){
            poly c = b; c.ins(a[i]);
            ans = max(ans, c.area());
        }
        return cout << ans, void();
    }
    db ans = 0;
    for(int i = 0; i < m; ++i)for(int j = i + 1, x = i, y = b.nex(i); j < m; ++j){
        for(; dcmp(hull :: S(b[j], b[i], b[x]) - hull :: S(b[j], b[i], b[b.nex(x)])) < 0; x = b.nex(x));
        for(; dcmp(hull :: S(b[i], b[j], b[y]) - hull :: S(b[i], b[j], b[b.nex(y)])) < 0; y = b.nex(y));
        ans = max(ans, hull :: S(b[j], b[i], b[x]) + hull :: S(b[i], b[j], b[y]));
    }
    cout << ans / 2;
}

浙江省选

我们先假设 \(k=1\),这时我们要做的就是求一个半平面交。但是考虑我们只需要求前 \(k\) 名并且 \(k\) 很小于是我们想暴力删掉一些线的贡献然后再做半平面交,就这样反复做 \(k\) 轮不就能得到前 \(k\) 大的了吗?然后我们考虑怎么干掉一条线的贡献。假设我们当前正在做第 \(i\) 轮,如果一个没被删掉的线对当前的半平面交有贡献就记录下贡献的范围删掉,这是一个区间加操作,为了方便我们可以离线差分处理。

具体的我们可以枚举每条还存在的线,我们需要考虑的应该是删掉现在的半平面交后对这些线有什么影响,于是我们需要有影响的区间,这个可以先二分出左右直线然后求一下交点。得到区间后你就直接差分,最后从左到右扫一遍统计答案即可。

inline void sol(int cur){
    q[top = tot = 0] = num(0, 1);
    for(int i = 1; i <= n; ++i)if(ans[id[i]] == - 1 and a[id[i]] > a[st[top]]){
        while(top and inter(id[i], st[top]).flr() < q[top].cl())--top;
        st[++top] = id[i]; if(top > 1)q[top] = inter(st[top - 1], st[top]);
    }
    q[top + 1] = num(INF, 1);
    for(int i = 1; i <= n; ++i)if(~ ans[i]){
        int l = 1, r = top - 1, res = top;
        while(l <= r){
            int mid = l + r >> 1;
            if(a[st[mid]] >= a[i] or inter(st[mid], i) <= q[mid + 1])res = mid, r = mid - 1;
            else l = mid + 1;
        }
        d[++tot] = make_pair(a[st[res]] >= a[i] ? 0ll : inter(st[res], i).flr() + 1, 1);
        l = 2, r = top, res = 1;
        while(l <= r){
            int mid = l + r >> 1;
            if(a[st[mid]] <= a[i] or q[mid] <= inter(st[mid], i))res = mid, l = mid + 1;
            else r = mid - 1;
        }
        if(a[st[res]] > a[i])d[++tot] = make_pair(inter(st[res], i).cl(), - 1);
    }
    sort(d + 1, d + 1 + tot);
    for(int i = 1, ps = 1, s = 0; i <= top; ++i){
        while(ps <= tot and d[ps].first <= q[i].cl())s += d[ps++].second;
        if(s < cur)ans[st[i]] = cur;
        while(ps <= tot and d[ps].first <= q[i + 1].flr()){
            int pos = ps; while(pos <= tot and d[pos].first == d[ps].first)s += d[pos++].second;
            if(s < cur)ans[st[i]] = cur; ps = pos;
        }
    }
}

逃考

一道非常好的题!

注意到 \(n\) 很小只有 \(600\),然后我们的代价与监控范围有关,所以我们能够想到先对每个人做半平面交求出其监控范围。我们考虑代价变化当且仅当小杨从一个人的监控范围跑到另一个范围于是我们考虑图论建模,将监控范围相邻的点连一条代价为一的边,然后在图上跑最短路即可。

说一下细节。因为这道题没说明亲戚的监控范围所以亲戚有可能在界外当老六,我们不予理会。关于起点与终点我们考虑小杨初始在谁的监控范围内就把起点设为谁,然后设置一个虚空终点,所有连通边界的点向终点连边即可。然后因为这个图的边权均为一所以你爱咋写咋写。

signed main(){
    ios :: sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    for(cin >> T; T--; ){
        cin >> n >> xlim >> ylim >> xs >> ys; a.clr();
        if(! n){cout << 0 << endl; continue;} a.rd(n); int _n = 0;
        for(int i = 0; i < n; ++i)if(a[i].x <= xlim and a[i].y <= ylim)a[_n++] = a[i];
        n = _n; for(int i = 0; i < n + 2; ++i)hd[i] = 0; cnt = 0;
        for(int i = 0; i < n; ++i){
            vector < line > b;
            b.push_back(line(pnt(0, 0), pnt(xlim, 0), n + 1));
            b.push_back(line(pnt(xlim, 0), pnt(xlim, ylim), n + 1));
            b.push_back(line(pnt(xlim, ylim), pnt(0, ylim), n + 1));
            b.push_back(line(pnt(0, ylim), pnt(0, 0), n + 1));
            for(int j = 0; j < n; ++j)if(i != j){
                pnt mid = (a[i] + a[j]) / 2, tmp = rotp(a[j], mid, 0.5 * pi);
                b.push_back(line(mid, tmp, j + 1));
            }
            hull :: SI(i + 1, b);
        }
        s = 0; t = n + 1; pnt bg = {xs, ys};
        for(int i = 1; i < n; ++i)if(dcmp(Len(a[s] - bg) - Len(a[i] - bg)) > 0)s = i;
        ++s; spfa();
    }
    return 0;
}

最小覆盖双圆问题

我们肯定想让两个圆尽量不交,因为这样一定不劣。设想如果两个圆有一些部分有交,我们可以尝试在满足覆盖所有点的情况下缩小两个圆,这样一定更优。于是我们肯定想找到一个分界点然后分别对两边做最小圆覆盖。一个最直接的想法就是按横坐标排序然后二分这个分界点,但这显然不对。那我们从答案形态入手,考虑其实将图形围绕原点旋转一定角度后两个部分的点一定能按新的横坐标分成两个区间,这启发我们去枚举旋转角度然后再二分分部点。然后你考虑如果旋转角度为其他的时候求到的答案一定不优所以这样做是可行的。

signed main(){
    ios :: sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    cout << fixed << setprecision (2);
    for(cin >> n; n; cin >> n){
        ans = inf; db th = rnd() % PI; th /= 10000000000.0;
        for(int i = 1; i <= n; ++i)cin >> a[i].x >> a[i].y, a[i] = rot(a[i], th);
        for(int t = 0; t < 180; ++t){
            sort(a + 1, a + 1 + n, [](pnt a, pnt b){return a.x < b.x;});
            int l = 1, r = n;
            while(l <= r){
                int mid = l + r >> 1; db r1 = mincov(1, mid).r, r2 = mincov(mid + 1, n).r;
                if(r1 > ans and r2 > ans)break;
                if(r1 < r2){l = mid + 1; if(r2 < ans)ans = r2;} else{r = mid - 1; if(r1 < ans)ans = r1;}
            }
            for(int i = 1; i <= n; ++i)a[i] = rot(a[i], ii);
        }
        cout << ans << endl;
    }
    return 0;
}

后记

因为计算几何的题难度大多都在代码上所以有很多题笔者并没有放上去。

posted @ 2025-08-16 15:56  Lyrella  阅读(7)  评论(0)    收藏  举报