XCPC区域赛计算几何题选

鸣谢参考材料:

区域赛计算几何

ICPC 2018 青岛H

给定一个三角形障碍物的三个点的坐标,一条线段的起终点坐标表示镜子,以及A , B点的坐标
现在有m个石头放在A 点,要将它们全部运到B点 并且满足如下规则:
1. 每次只能带个石头
2. 所有石头只能在B点放下
3. 路径上的每个点必须能够看到所有石头
4. 障碍物和镜子都不能穿过,镜子只有一面反光
求走过的最短距离.

首先, 若 A , B A,B A,B 可直视, 则 a n s = ( 2 m − 1 ) ∗ ∣ A B ∣ ans=(2m-1)*|AB| ans=(2m1)AB.
剩余情况, 注意镜子只有右边能反射, 所以如果 A / B A/B A/B 在镜子左侧, 则无法通过镜子看到.

  • m = 1: 一定有解. 沿着给定的7个点跑最短路即可.

  • m > 1: 有解当且仅当 A , B A,B A,B 可以直视 或通过镜子互相看到(作B关于镜子的对称点 B’, AB’与三角形内部不交 且 AB’与镜子有交).
    第一次去程需要保证能时刻看到 A A A, 最后的去程需要保证能时刻看到 B B B, 中间的 2 m − 3 2m-3 2m3 次来/回需要同时保证能看到 A , B A,B A,B.
    考虑改变可见性的5类直线:

    • 起点 / 终点和障碍物顶点的连线。
    • 起点 / 终点和镜面端点的连线。
    • 起点 / 终点的镜像和镜面端点的连线。
    • 起点 / 终点的镜像和障碍物顶点的连线。
    • 起点 / 终点的镜像和障碍物顶点镜像的连线。
      然后只有这些直线交点(在镜子右侧)才是有用的, 用这些直线的交点更新最短路即可.
      而A,B的可见区域显然为凸的区域. 对于线段 seg, 我们考察移动对 A的可见性不影响, 则只需关心 seg和上述直线的交点, 将这些点和seg端点排序,取相邻点的中点看可见性即可.
const int N = 210;
namespace geo {//Geometry几何
    typedef double db;
    typedef long double ld;
    const int N = 210;
    const db eps = 1e-8, inf = 1.0 / 0.0, PI = acosl(-1.0); //精度
    int sign(db x) {return x < -eps ? -1: (x > eps ? 1: 0);} // 符号位
    
    struct P { // Point
        db x, y;
        P(db x = 0, db y = 0):x(x),y(y){}
        P operator +(P b) const {return P(x + b.x, y + b.y);}
        P operator -(P b) const {return P(x - b.x, y - b.y);}
        P operator *(db t) const {return P(x * t, y * t);} // 数乘
        P operator /(db t) const {return P(x / t, y / t);} 
        friend db dot(P a, P b) {return a.x * b.x + a.y * b.y;}//点乘
        db operator *(P b) const {return x * b.y - y * b.x;} // 叉积
        db operator ^(P b) const {return x * b.x + y * b.y;} // 点乘
        db len() {return hypotl(x, y);} //求模长
        friend db dis(P a, P b) {return (a - b).len();} // 两点距离
        friend P normal(P a) {return P(-a.y, a.x);} //法向量(逆时针旋转90度)
        friend P unit(P a) {return a / a.len();} //单位化
        friend db slope(P a, P b) {return (b.y - a.y) / (b.x - a.x);} // 斜率
        friend db angle(P a, P b) {return atan2(b.y - a.y, b.x - a.x); } // b -> a 的极角
        friend db area(P a, P b, P c) {return fabs((c - a) * (b - a)) / 2;} // 三角形面积
        
        bool operator <(const P &b) const {return sign(x - b.x) ? x < b.x: y < b.y;}
        bool operator ==(const P &b) const {return sign(x - b.x) == 0 && sign(y - b.y) == 0;}
        
        P turn(db t) { //顺时针旋转 t(theta)(弧度)
            return P(x * cos(-t) + y * sin(t), x * sin(-t) + y * cos(t));
        }
        P Turn(P b, db t) {// 绕b点顺时针旋转 t(theta)(弧度)
            P c = *this - b;
            return c.turn(t) + b;
        }
        
        void in() {scanf("%lf %lf", &x, &y);}
        void out() {printf("%.5lf %.5lf\n", x, y);} // 注意输出位数
    } q[N]; // 注意q是用来存凸包的
    
    int judge(P a, P b, P c) {return sign((b - a) * (c - a)) >= 0;} // a,b,c严格逆时针(有序)
    int L_have(P s, P t, P p) {return !sign((p - s) * (t - s));}//直线上有点p
    int S_have(P s, P t, P p) {return !sign((p - s) * (t - s)) && sign(dot(p - s, p - t)) <= 0;} //线段上有点p
    
    int PICP(P *a, int n, P p) { // Point In Convex Polygon. p是否在凸多边形a外 (0: 内. 1: 边界, 2: 外)
        if(judge(a[1], p, a[2]) || judge(a[n], p, a[1])) return 2;
        if(S_have(a[1], a[2], p) || S_have(a[1], a[n], p)) return 1;
        int l = 2, r = n - 1, mid;
        while(l < r) { // 以 a[1] 为极点, 看p的极角在哪条边中间
            mid = (l + r + 1) >> 1;
            if(judge(a[1], a[mid], p)) l = mid;
            else r = mid - 1;
        }
        if(judge(a[l], p, a[l + 1])) return 2;
        return S_have(a[l], a[l + 1], p);
    }
    
    int PIP(int *a, int n, P p) { // Point in Polygon. (点的顺序是逆/顺时针给出) (return 0: 内. 1: 边界, 2: 外)
        // 回转数算法.
        a[n + 1] = a[1];
        FOR(i, n) if(S_have(a[i], a[i + 1], p)) return 1;
        db sum = 0, l = angle(p, a[1]), r, d;
        FOR(i, n) {
            r = angle(p, a[i + 1]);
            d = r - l;
            if(d >= PI) d -= 2 * PI;
            else if(d < -PI) d += 2 * PI;
            sum += d;
        }
        // 内部 sum = 2PI, 外部 sum = 0. 这里简单以PI为分界线
        return abs(sum) < PI ? 2: 0;
    }
    
    // #define Half // 半平面交时要求斜率的弧度
    struct L { //Line: 线段(segment/ S), 射线(ray/ R), 直线(line/ L)
        P s, t; 
        L(P s, P t):s(s), t(t){init();}
        L(){}
        void in() {s.in(); t.in(); init();}
        
        #ifdef Half 
            ld k; // 只有半平面交时需要的极角
        #endif
        void init() {
            #ifdef Half
                k = angle(s, t);
            #endif
        }
        #ifdef Half
            bool operator <(L b) const {
                return sign(b.k - k) ? k < b.k: b.judge(s);
            }
        #endif
        
        db len() {return (t - s).len();} // 线段长度
        int judge(P b) {return !geo::judge(s, t, b);}// 右侧
        db crossRatio(L b) {
            if(sign((t - s) * (b.t - b.s)) == 0) return inf;
            return ((b.s - s) * (b.t - b.s))  /  ((t - s) * (b.t - b.s));
        }
        P R2P(db r) {return s + (t - s) * r;} // Ratio to Point
        VT<P> operator &(L b) { //直线交点
            db r = crossRatio(b);
            if(r == inf) return {};
            return {R2P(r)};
        }
        // * 平行或共线不算交
        bool S_cross(L b, bool s = 0) { //线段是否有交. s:strict. s=1时, 交点不能是端点.
            db x = crossRatio(b), y = b.crossRatio(*this);
            return sign(x) >= s && sign(1 - x) >= s && 
                   sign(y) >= s && sign(1 - y) >= s;
        }
        db P2S(P p) { // 点p到线段的距离
            if(s == t) return (s - p).len();
            P x = p - s, y = p - t, z = t - s;
            if(sign(dot(x, z)) < 0) return x.len();
            if(sign(dot(y, z)) > 0) return y.len();
            return fabs((x * z) / z.len()); // 点到直线距离
        }
        db P2L(P p) {// 点到直线距离
            return 2 * area(s, t, p) / dis(s, t); 
        }
        P Foot_point(P p) { //点到直线的垂足
            P x = p - s, y = p - t, z = t - s;
            db u = dot(x, z), v = -dot(y, z); //求投影
            return s + z * (u / (u + v));
        }
        P Sym_point(P p) {//symmetry point 对称点
            return Foot_point(p) * 2 - p;
        }
    };
    
    P s, t, a[4], ma[4], m1, m2;
    L mr, poly[3], mpoly[3];
    int n, m;
    
    bool check(L seg) {
        if(mr.S_cross(seg, 1)) return 0;
        rep(i, 0, 2) if(poly[i].S_cross(seg, 1)) return 0;
        return 1;
    }
    
    int see(P p, P g) {
        if(check(L(p, g))) return 1; // see directly
        L seg(p, mr.Sym_point(g));
        if(!mr.S_cross(seg, 0)) return 0;
        rep(i, 0, 2) {
            if(poly[i].S_cross(seg, 1)) return 0;
            if(mpoly[i].S_cross(seg, 1)) return 0;
        }
        return 1;
    }
    
    int see(L seg, P g, VT<L> &border) {
        VT<P> p = {seg.s, seg.t};
        for(auto &l: border) {
            auto r = seg.crossRatio(l);
            if(eps < r && r < 1 - eps) p.pb(seg.R2P(r));
        }
        sort(p);
        FOR(i, SZ(p) - 1) {
            auto x = (p[i] + p[i - 1]) / 2;
            if(!see(x, g)) return 0;
        }
        return 1;
    }
    
    VT<pair<int, db>> e[N];
    
    void build1() {
        VT<P> keys = {s, t, m1, m2, a[0], a[1], a[2]};
        n = SZ(keys);
        rep(i, 0, n - 1) e[i].clear();
        rep(i, 0, n - 1) rep(j, i + 1, n - 1) {
            L seg(keys[i], keys[j]);
            if(check(seg)) {
                e[i].pb(mk(j << 2, seg.len()));
                e[j].pb(mk(i << 2, seg.len()));
            }
        }
    }
    
    void build2() {
        VT<L> B;
        B.pb(L(s, m1));
        B.pb(L(s, m2));
        B.pb(L(t, m1));
        B.pb(L(t, m2));
        rep(i, 0, 2) {
            B.pb(L(s, a[i]));
            B.pb(L(t, a[i]));
        }
        {
            P s = mr.Sym_point(geo::s), t = mr.Sym_point(geo::t);
            B.pb(L(s, m1));
            B.pb(L(s, m2));
            B.pb(L(t, m1));
            B.pb(L(t, m2));
            rep(i, 0, 2) {
                B.pb(L(s, a[i]));
                B.pb(L(s, ma[i]));
                B.pb(L(t, a[i]));
                B.pb(L(t, ma[i]));
            }
        }
        VT<P> keys = {s, t};
        for(auto &x: B) for(auto &y: B) for(auto z: x & y)
            if(mr.judge(z) && find(all(keys), z) == keys.end()) keys.pb(z);
        n = SZ(keys);
        rep(i, 0, n - 1) e[i].clear();
        rep(i, 0, n - 1) rep(j, i + 1, n - 1) {
            L seg(keys[i], keys[j]);
            if(!check(seg)) continue;
            int k = see(seg, s, B) + see(seg, t, B) * 2;
            e[i].pb({(j << 2) + k, seg.len()});
            e[j].pb({(i << 2) + k, seg.len()});
        }
    }
    
    db dijkstra(int MSK) {
        static db d[N];
        fill(d, d + n, inf);
        d[0] = 0; 
        priority_queue<pair<db, int>, VT<pair<db, int>>, greater<>> q;
        q.push({0, 0});
        while(q.size()) {
            auto [dis, x] = q.top(); q.pop();
            if(sign(dis - d[x])) continue;
            for(auto [st, w]: e[x]) {
                int y = st >> 2, z = st & 3;
                if((MSK & z) == MSK && cmin(d[y], dis + w)) 
                    q.push({d[y], y});
            }
        }
        return d[1];
    }
    
    void solve() {
        qr(m);
        s.in(); t.in(); 
        m1.in(); m2.in(); mr = L(m1, m2);
        rep(i, 0, 2) a[i].in(), ma[i] = mr.Sym_point(a[i]);
        a[3] = a[0]; 
        ma[3] = ma[0];
        rep(i, 0, 2) {
            poly[i] = L(a[i], a[i + 1]);
            mpoly[i] = L(ma[i], ma[i + 1]);
        }
        db ans = -1;
        if(check(L(s, t))) {
            ans = (2 * m - 1) * dis(s, t);
        }
        else if(m == 1) {
            build1();
            ans = dijkstra(0);
        }
        else if(see(s, t) && mr.judge(s) && mr.judge(t)) {
            build2();
            ans = dijkstra(1) + dijkstra(2) + dijkstra(3) * (2 * m - 3);
        }
        
        if(ans < 0) pr2(-1);
        else printf("%.12lf\n", ans);
    }
}

ICPC 2020 南京I

你在二维平面溜冰, 可行区域为 x ∈ [ − m , m ] x\in [-m, m] x[m,m], 有 n n n 个线段障碍, 你的溜冰速度的竖直分量恒定为 v y v_y vy, 你可以控制你的水平分速度在 [ − v x , v x ] [-v_x, v_x] [vx,vx] 之间, 问是否存在最小的 v x ∗ v_x^* vx 使得当 v x > v x ∗ v_x > v_x ^* vx>vx 时你可以从 ( 0 , − ∞ ) (0,-\infty) (0,) 出发, 顺利地不经过所有障碍 (到达 ( 0 , + ∞ ) (0, +\infty) (0,+)).
1 ≤   n   ≤   100 ,    1   ≤   m   ≤    1 0 4 1\le\,n\,\le\,100,\;1\,\le\,m\,\le\,\,10^{4} 1n100,1m104, 任意两个障碍不交.

注意到 任意两个障碍不交, 故无解当且仅当存在一个障碍的端点的x坐标集合为 { − m , m } \{-m, m\} {m,m}.
考虑按 y y y 递增的方式转移, 一条线段转移合法当且仅当它和其他障碍不交, 这步移动最小的 v x = v y ∣ s l o p e ∣ v_x = \dfrac{v_y}{|slope|} vx=slopevy.

虚拟出源汇点 ( 0 , − ∞ ) , ( 0 , ∞ ) (0, -\infty), (0,\infty) (0,),(0,), 在加一个出发点 ( 0 , − 1 0 5 ) (0, -10^5) (0,105) 即可.

ICPC2020 沈阳站A题

给一个矩形和两条其中的线段,问到两线段最小距离相等的(在矩形内的)点集的面积大小.
$ T\le 10^5, |x|,|y|\le 10^3$

A B , C D AB,CD AB,CD 重合, 则答案为矩形的面积.

否则, 对于线段 A B AB AB, 我们在端点作垂线 l A , l B l_A,l_B lA,lB, 对于 l A l_A lA 外侧点, 它们到线段的距离就是到 A A A 的距离, 同理对于 l B l_B lB 外侧点, 它们到线段的距离就是到 B B B 的距离. 而 l A , l B l_A, l_B lA,lB 之间的点到线段 A B AB AB 的距离就是到直线 A B AB AB 的距离.

  • 到两不重合直线距离相等的点集是直线, 面积为0.
  • 到两重合直线距离相等的点集为全平面, 需要考虑.
  • 到两不同点距离相等的点集是中垂线, 面积也为0.
  • 到两个相同点距离相等的点集是全平面, 这是需要考虑的情况.
  • 到一点,一直线距离相等的情况, 这时候图像是抛物线, 面积也为0.

求合法答案我们直接用半平面交计算即可.

由于半平面数量最多为8,故复杂度为 O ( T ∗ 8 log ⁡ 8 ) O(T * 8\log 8) O(T8log8).

注意: 本题精度要求较高(绝对误差或相对误差 < 1 0 − 9 < 10^{-9} <109), 建议使用 long double.
不过我使用double时, 将 e p s eps eps 设为 5 e − 10 5e-10 5e10 也能过.

P a, b, c, d, p[N];
L border[N], l[N];

db calc(VT<L> a) {
    int tot = 0;
    FOR(i, 4) l[++tot] = border[i];
    for(auto x: a) l[++tot] = x;
    tot = Half_plane(l, tot, p);
    return Area(p, tot);
}

void solve() {
    db ans = 0, S = 0;
    a.in(); b.in();
    S = (b.x - a.x) * (b.y - a.y);
    assert(b.x > a.x);
    assert(b.y > a.y);
    border[1] = L(a, P(b.x, a.y));
    border[2] = L(P(b.x, a.y), b);
    border[3] = L(b, P(a.x, b.y));
    border[4] = L(P(a.x, b.y), a);
    a.in(); b.in(); c.in(); d.in();
    if(b < a) swap(a, b);
    if(d < c) swap(c, d);
    if(a == c && b == d) ans = S;
    else {
        L AB(a, b);
        P h1 = normal(b - a), h2 = normal(d - c); // 法向量
        L LA(a + h1, a), LB(b, b + h1), LC(c + h2, c), LD(d, d + h2); // 指向半平面线段内部
        if(AB.P2L(c) < eps && AB.P2L(d) < eps)  // 直线重合
            ans += calc({LA, LB, LC, LD});
        if(a == c) ans += calc({~LA, ~LC}); // ~LA表示逆向半平面 
        if(a == d) ans += calc({~LA, ~LD});
        if(b == c) ans += calc({~LB, ~LC});
        if(b == d) ans += calc({~LB, ~LD});
    }
    printf("%.15lf\n", ans);
}

ICPC2020 昆明站I题

有一辆火车沿着一条线段行驶, 它一侧有 n n n 个风车, 询问第 h h h 号风车和其他风车 第 k k k 次发生视野左右交替的火车位置.
n ≤ 1000 n\le 1000 n1000.

直接 O ( n 2 ) O(n^2) O(n2) 求所有交点, 按照比例排序即可.

P p[N];
VT<db> a[N];

void solve() {
    int n, m; qr(n, m);
    L s; s.in();
    FOR(i, n) p[i].in();
    FOR(i, n) {
        FOR(j, n) if(i != j) {
            auto r = s.crossRatio(L(p[i], p[j]));
            if(0 < r && r < 1) a[i].pb(r);
        }
        sort(a[i]);
    }
    int h, k;
    while(m--) {
        qr(h, k);
        if(k-- > SZ(a[h])) pr2(-1);
        else s.R2P(a[h][k]).out();
    }
}

ICPC2021 澳门站C题

n n n个点, 它们两两之间连线, 问最少删掉多少个点, 使得你可以从原点走到无穷远处.
n ≤ 1 0 6 n\le 10^6 n106.

转化题意: 剩余点的凸包不包含原点.
那么只需要按照极角排序, two pointer 扫描即可.
需要注意共线且反方向的情况.
如果要求极角需要将参数强制转化为 long double, 而下面的代码展示了一种不需要求显式求极角也能按极角排序的方法.

struct P {
    ll x, y;
    int cnt;
    void in() {
        qr(x), qr(y);
        ll z = abs(__gcd(x, y));
        x /= z, y /= z;
        cnt = 1;
    }
    ll operator *(const P& b) const {return x * b.y - y * b.x;}
    bool operator ==(const P& b) const {return x == b.x && y == b.y;}
    int val() const {return y > 0 || (y == 0 && x > 0);} // 分界线, 极角是否在 [0, PI) 中
    bool operator <(const P &b) {
        if(val() != b.val()) return val() > b.val();
        return *this * b > 0;
    }
} ;

void solve() {
    qr(n);
    vector<P> a(n);
    rep(i, 0, n - 1) a[i].in();
    sort(a.begin(), a.end());
    m = 0;
    FOR(i, n - 1) 
        if(a[i] == a[m]) a[m].cnt++;
        else a[++m] = a[i];
    n = ++m;
    if(n == 1) return pr2(0);
    int ans = N, sum = 0, p = 1;
    rep(i, 0, n - 1) {
        while(a[i] * a[p] >= 0) {
            sum += a[p].cnt;
            int t = p++;
            if(p == n) p = 0;
            if(i == p) return pr2(a[i] * a[t] == 0 ? min(a[i].cnt, a[t].cnt): 0);
        }
        ans = min(ans, sum);
        sum -= a[i + 1].cnt;
    }
    pr2(ans);
}

CCPC2021 桂林F题

给两个凸多边形, 小凸包严格在大凸包内部, 询问大凸包上的点看到小凸包上边界的期望长度.
n , m ≤ 2 e 5 n,m\le 2e5 n,m2e5.

考虑小凸包每条边的贡献: 即一个半平面和大凸包的交集的周长, 用two pointer 技巧扫描即可, 为了避免循环的麻烦, 我们直接复制大凸包的点数为原来的3倍.
注意值域大, eps小, 一定要用 long double, eps 要比题目误差限的更小.

P a[N * 3], b[N];
int n, m;
L seg[N];
db sum[N * 3];

void solve() {
    qr(n, m);
    FOR(i, n) a[i].in();
    FOR(i, m) b[i].in();
    b[m + 1] = b[1];
    FOR(i, m) seg[i] = L(b[i + 1], b[i]);
    FOR(i, n) if(!seg[1].judge(a[i])) {
        rotate(a + 1, a + i, a + n + 1);
        break;
    }
    copy(a + 1, a + n + 1, a + n + 1);
    copy(a + 1, a + n + 1, a + 2 * n + 1);
    rep(i, 2, 3 * n) sum[i] = sum[i - 1] + dis(a[i], a[i - 1]);
    db C = sum[n + 1];
    int x = 1, y = 0;
    db ans = 0;
    FOR(i, m) {
        db d = seg[i].len();
        auto now = seg[i];
        while(!now.judge(a[x])) x++;
        cmax(y, x);
        while(now.judge(a[y])) y++;
        db len = sum[y - 1] - sum[x] + dis(now & L(a[x - 1], a[x]), a[x]) + dis(now & L(a[y - 1], a[y]), a[y - 1]);
        ans += len * d;
        // debug(len);
        // debug(d);
    } 
    printf("%.15LF\n", ans / C);
}

ICPC2021 昆明L题

在一个无限的二维平面上有 n 颗星星,每颗星星都发出相同的不相交的k片光区域。光的每个区域都以角度的形式给出( [ 0 , 180 ° ) [0, 180°) [0,180°)), 角度区间不交. 对于每一颗星星,求它被其他星星照亮的次数。星星不会遮挡光线。

考虑每个角度区间的贡献.
如果角度区间两端都不是 90°, 那么直接求点关于两个角度的y轴截距, 这两个截距为新坐标 ( x ′ , y ′ ) (x',y') (x,y).
r = 90 ° r= 90° r=90°, 则 y ′ = − x y' = -x y=x.
l = 90 ° l=90° l=90°, 则 x ′ = x x'= x x=x.
r < 90 ° r<90° r<90°, 在锐角区域, x ′ x' x 小, y ′ y' y 大, 获得一次照亮.
l > 90 ° l>90° l>90°, 在钝角区域, x ′ x' x 大, y ′ y' y 小, 获得一次照亮.
l ≤ 90 ° , r ≥ 90 ° l\le 90°, r\ge 90° l90°,r90°, x ′ x' x 小, y ′ y' y 小, 获得一次照亮.

k k k 次二维偏序, 复杂度为 O ( k n log ⁡ n ) O(k n\log n) O(knlogn).

const int N = 1e5 + 10, inf = 0x3f3f3f3f;
const db PI = acos(-1.0), eps = 1e-8;
int sign(db x) {return fabs(x) < eps ? 0: (x > 0? 1: -1);}

int n, m;

struct P {
    db x, y;
    int id;
    P(db x = 0, db y = 0, int id=0):x(x),y(y),id(id){}
    void in(int i) {qr(x, y); id = i;}
    void out() {printf("%.9lf %.9lf %d\n", x, y, id);}
    db intercede(int ang, int id) {
        if(ang == 90) return id == 1 ? x: -x;
        db t = tan(ang * PI / 180);
        return y + t * x;
    }
    P coord(int l, int r) {
        int o = 3;
        if(r < 90) o = 1;
        else if(l > 90) o = 2;
        
        return P((o == 2? 1: -1) * intercede(l, 1), (o == 1 ? 1: -1) * intercede(r, 2), id);
    }
} a[N], b[N];

bool cmp_x(P a, P b) {return sign(a.x - b.x) ? a.x < b.x : a.y < b.y;}
bool cmp_y(P a, P b) {return sign(a.y - b.y) <= 0;}

int ans[N];

void solve(int l, int r) {
    if(l == r) return;
    int mid = (l + r) >> 1;
    solve(l, mid); 
    solve(mid + 1, r);
    int x = l, y = mid + 1, z = l;
    static P tmp[N];
    while(x <= mid && y <= r) {
        if(cmp_y(b[x], b[y])) tmp[z++] = b[x++];
        else ans[b[y].id] += x - l, tmp[z++] = b[y++];
    }
    while(x <= mid) tmp[z++] = b[x++];
    while(y <= r) ans[b[y].id] += x - l, tmp[z++] = b[y++];
    rep(i, l, r) b[i] = tmp[i];
}

void solve() {
    qr(n, m);
    FOR(i, n) a[i].in(i);
    int l, r;
    while(m--) {
        qr(l, r);
        FOR(i, n) b[i] = a[i].coord(l, r);
        sort(b + 1, b + n + 1, cmp_x);
        solve(1, n);
    }
    FOR(i, n) pr1(ans[i]);
}

CF 计算几何

CF1860F Evaluate RBS

2 n 2n 2n 个pair(a,b,c). 判断是否存在 x , y > 0 x, y >0 x,y>0, 使得按照 a x + b y ax+by ax+by 排序后, c可以排列成合法括号序

我们显然可以钦定 y = 1 y=1 y=1, 则 x = − k x=-k x=k (k为两点间的斜率)
任意两个点的相对位置变化只会随着 ∣ k ∣ |k| k 的增大而至多变化1次.
使用线段树维护顺序即可.
需要特别注意的是, 对于一条直线上的点, 先让 ‘(’ 位置提前.

const int N = 3010, inf = 0x3f3f3f3f;
const ll INF = 4E18;

namespace SEG {
    #define DEF int x, int l, int r
    #define QDEF DEF, int L, int R
    #define mid ((l + r) >> 1)
    #define lc (x << 1)
    #define rc (lc | 1)
    #define LC lc, l, mid
    #define RC rc, mid + 1, r
    #define QLC LC, L, R
    #define QRC RC, L, R
    
    struct node {
        int sum, mi; // 区间和, 前缀和最小值
        node(int x = 0, int y = 0):sum(x), mi(y){}
        node operator +(const node &b) const {
            node c = *this;
            c.sum += b.sum;
            cmin(c.mi, b.mi + sum);
            return c;
        }
    } tr[N << 2];
    int p[N]; // 维护顺序 p[i]表示 id=i的对应位置
    int *v, n;
    void upd(int x) {tr[x]=tr[lc] +tr[rc];}
    void bt(DEF) {
        if(l == r) {
            tr[x] = node(v[l], v[l]);
            return;
        }
        bt(LC); bt(RC);
        upd(x);
    }
    void change(DEF, int p, int v) {
        if(l == r) {
            tr[x] = node(v, v);
            return;
        }
        if(p <= mid) change(LC, p, v);
        else change(RC, p, v);
        upd(x);
    }
    
    bool build(int n, int *a) {
        v = a; SEG::n = n;
        FOR(i, n) p[i] = i;
        bt(1, 1, n);
        return tr[1].mi >= 0;
    }
    bool swap(int x, int y) {
        int vx = v[x], vy = v[y];
        int &px = p[x], &py = p[y];
        if(px > py) return 0;
        change(1, 1, n, px, vy);
        change(1, 1, n, py, vx);
        std::swap(px, py);
        return tr[1].mi >= 0;
    }
    
    #undef DEF
    #undef QDEF
    #undef mid
    #undef lc 
    #undef rc 
    #undef LC 
    #undef RC 
    #undef QLC 
    #undef QRC 
}

int n, m, v[N];
array<int, 3> a[N];
const db eps = 1e-12;

struct SL {//slope
    int x, y;
    bool operator <(SL b) const {
        return 1LL * x * b.y > 1LL * y * b.x;
    }
    bool operator == (SL b) const {
        return 1LL * x * b.y == 1LL * y * b.x;
    }
};

struct U {
    SL k; int x, y;
    bool operator <(const U &b) const {
        return k != b.k ? k < b.k : (v[y] - v[x] > v[b.y] - v[b.x]);
        // 先让 '(' 上移
    }
};

void solve() {
    qr(n); n *= 2;
    char ch;
    FOR(i, n) {
        qr(a[i][1], a[i][0], ch);
        a[i][2] = ch == '(' ? -1: 1;
    }
    sort(a + 1, a + n + 1);
    FOR(i, n) v[i] = -a[i][2];
    if(SEG::build(n, v)) return YES(1);
    VT<U> ev;
    FOR(i, n) rep(j, i + 1, n) if(a[i][1] ^ a[j][1]) {
        int dy = a[j][0] - a[i][0], dx = a[j][1] - a[i][1];
        db k = (db)dy / dx;
        if(k >= 0) continue;
        ev.pb(U{SL{dx, -dy}, i, j});
    }
    sort(ev);
    for(auto [_, x, y]: ev) 
        if(SEG::swap(x, y)) return YES(1);
    YES(0);
}

CF1841F. Monocarp and a Strategic Game

显然, 令 x i = b i − a i , y i = d i − c i x_i = b_i-a_i, y_i = d_i-c_i xi=biai,yi=dici, 则我们需要确定一组 0/1 系数 { t i } \{t_i\} {ti}
使得 m a x i m i z e    ( ∑ t i ∗ x i ) 2 + ( ∑ t i ∗ y i ) 2 maximize ~~ (\sum{t_i *x_i})^2+(\sum{t_i *y_i})^2 maximize  (tixi)2+(tiyi)2.

使用 闵科夫斯基和 维护凸包即可.

typedef complex<long double> P;
P a, b, s;

void solve() {
    qr(n);
    int x, y, z, w;
    a = {1, 0};
    debug(arg(a));
    pr2(-a);
    debug(arg(-a));
    debug(arg(P(-1,0)));
    s = {0, 0};
    VT<pair<double, P>> v;
    FOR(i, n) {
        qr(x, y, z, w);
        a = P(x -= y, z -= w);
        b = P(-x, -z);
        if(arg(a) > 0) s += a;
        v.pb({arg(a), a});
        v.pb({arg(b), b});
    }
    sort(all(v), [](auto x, auto y){return x.fi < y.fi;});
    ld ans = norm(s);
    for(auto [_, a]: v) s += a, cmax(ans, norm(s));
    pr2(ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值