Submission #35203333


Source Code Expand

// O(N^3 + MAX^2) 時間 / 全部整数 (MAX^7 * 3^7 の精度が必要)
// O(N^3) 凸包 (2 点とって辺を凸包に採用できるか)
// 半空間の積に分解
// z 座標で切って半平面の積
// y 座標で切ってしゃくとり
// 2D は gcd をうまく使うと単位格子の面積を求められる
#include <bits/stdc++.h>
#include <atcoder/modint>
using namespace std;
using ll = long long;
using Modint = atcoder::modint998244353;
bool chmin(ll& a, ll b){ if(a <= b) return 0; a = b; return 1; }
bool chmax(ll& a, ll b){ if(a >= b) return 0; a = b; return 1; }
template<class T, class F = equal_to<>> void uniq(T& A, F f = equal_to<>{}){
    A.erase(unique(A.begin(), A.end(), f), A.end());
}
template<class T, class F> void remove_if(T& A, F f){
    A.erase(remove_if(A.begin(), A.end(), f), A.end());
}
ll div_floor(ll a, ll b){
    assert(b > 0);
    ll ans = a / b;
    if(a % b < 0) ans--;
    return ans;
}
ll div_ceil(ll a, ll b){
    assert(b > 0);
    ll ans = a / b;
    if(a % b > 0) ans++;
    return ans;
}
ll gcd(ll a, ll b, ll c){
    return gcd(a, gcd(b, c));
}


struct Point{
    ll x = 0, y = 0, z = 0;
    Point& operator-=(Point a){
        x -= a.x; y -= a.y; z -= a.z;
        return *this;
    }
    Point& operator/=(ll a){
        x /= a; y /= a; z /= a;
        return *this;
    }
    Point operator-() const {
        return {-x, -y, -z};
    }
    friend bool operator==(Point a, Point b){
        return a.x == b.x && a.y == b.y && a.z == b.z;
    }
    friend bool operator!=(Point a, Point b){
        return a.x != b.x || a.y != b.y || a.z != b.z;
    }
    friend bool operator<(Point a, Point b){
        return tie(a.x, a.y, a.z) < tie(b.x, b.y, b.z);
    }
    friend bool operator>(Point a, Point b){
        return tie(a.x, a.y, a.z) > tie(b.x, b.y, b.z);
    }
    friend Point operator+(Point a, Point b){
        return {a.x + b.x, a.y + b.y, a.z + b.z};
    }
    friend Point operator-(Point a, Point b){
        return {a.x - b.x, a.y - b.y, a.z - b.z};
    }
    friend Point operator*(ll a, Point b){
        return {b.x * a, b.y * a, b.z * a};
    }
    friend ll dot(Point a, Point b){
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }
    friend Point cross(Point a, Point b){
        return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
    }
};
bool one_line(Point a, Point b){
    return cross(a, b) == Point{};
}
bool in_segment(Point a, Point b, Point x){
    a -= x;
    b -= x;
    return one_line(a, b) && dot(a, b) <= 0;
}

Modint solve1D(Point a, Modint K){
    // 全ての点が 1 直線上にある場合
    return K * gcd(a.x, a.y, a.z) + 1;
}
Modint solve2D(vector<Point> poly, Modint K){
    // 全ての点が 1 平面上にある場合
    // 辺上の格子点の数、面積 / 単位格子の面積 を求めてエルハート多項式 (ピックの定理)
    const ll N = poly.size();
    vector<Modint> ehrhart = {1, 0, 0};
    // 面積 / 単位格子の面積
    for(ll i = 1; i + 1 < N; i++){
        Modint area = 1;
        Point u = poly[i] - poly[0], v = poly[i + 1] - poly[0];
        {
            const ll g = gcd(u.x, u.y, u.z);
            u /= g;
            area *= g;
        }
        {
            const ll g = gcd(v.x, v.y, v.z);
            v /= g;
            area *= g;
        }
        u = cross(u, v);
        area *= gcd(u.x, u.y, u.z);
        ehrhart[2] += area;
    }
    // 辺上の格子点の数
    poly.push_back(poly[0]);
    for(ll i = 0; i < N; i++){
        const Point v = poly[i + 1] - poly[i];
        ehrhart[1] += gcd(v.x, v.y, v.z);
    }
    ehrhart[2] /= 2;
    ehrhart[1] /= 2;
    return ((ehrhart[2] * K) + ehrhart[1]) * K + ehrhart[0];
}
Modint solve(vector<Point> A, Modint K){
    // 同一直線上に 3 点あれば内側の点を削除
    for(ll i = A.size(); i--; ) if([&]() -> bool {
        for(ll j = A.size(); j--; ) if(j != i){
            for(ll k = j; k--; ) if(k != i){
                // A[j] - A[i] - A[k] の順に並んでいるか?
                if(in_segment(A[j], A[k], A[i])) return 1;
            }
        }
        return 0;
    }()) A.erase(A.begin() + i);

    // 全ての点が 1 直線上にある場合
    if(A.size() == 2) return solve1D(A[1] - A[0], K);

    // 凸包の辺を列挙
    vector<pair<ll, ll>> edge;
    vector g(A.size(), vector<ll>{});
    for(ll i = A.size(); i--; ) for(ll j = i; j--; ) if([&]() -> bool {
        // 軸 A[i] - A[j] 回りの 180 度未満の領域に全ての点があるか?
        if(A.size() <= 3) return 1;
        vector<Point> B = A;
        const Point U = B[i];
        B.erase(B.begin() + i);
        for(Point& x : B) x -= U;
        const Point V = B[j];
        B.erase(B.begin() + j);
        // 軸成分 (V) を取り除いたときの cross == cross の軸成分 なので、軸成分を取り除かなくても良い
        // 偏角ソート
        B = [&]{
            // 2 分割してループを切り開いてソート、同じ向きは unique
            // 2 分割の方法 : 基準のベクトルを持ってきて cross が正か / 0 の場合は V と cross を取って 0 と比較
            vector<Point> upper, lower;
            const Point W = B[0];
            for(Point x : B){
                ll c = dot(V, cross(W, x));
                if(c == 0) (cross(V, x) < Point{} ? lower : upper).push_back(x);
                else (c < 0 ? lower : upper).push_back(x);
            }
            sort(lower.begin(), lower.end(), [&](Point a, Point b){ return dot(cross(a, b), V) > 0; });
            uniq(lower, [&](Point a, Point b){ return dot(cross(a, b), V) == 0; });
            sort(upper.begin(), upper.end(), [&](Point a, Point b){ return dot(cross(a, b), V) > 0; });
            uniq(upper, [&](Point a, Point b){ return dot(cross(a, b), V) == 0; });
            upper.insert(upper.end(), lower.begin(), lower.end());
            return upper;
        }();
        // 180 度より大きく開いている部分が 1 箇所あれば OK
        assert(B.size());
        if(B.size() == 1) return 1;
        B.push_back(B.front());
        return adjacent_find(B.begin(), B.end(), [&](Point a, Point b){ return dot(cross(a, b), V) < 0; }) != B.end();
    }()){
        edge.emplace_back(i, j);
        g[i].push_back(j);
        g[j].push_back(i);
    }

    // 辺 2 本で面を作る
    vector<pair<Point, ll>> planes;
    auto add_plane = [&](Point a, Point b, Point c){
        // 面 abc の方程式を求める
        // dot(cross(b - a, c - a), x - a) == 0
        // dot(cross(b - a, c - a), x) == dot(cross(b - a, c - a), a)
        Point abc = cross(b - a, c - a);
        // 正規化
        if(abc < Point{}) abc = -abc;
        const ll g = gcd(abc.x, abc.y, abc.z);
        abc /= g;
        planes.emplace_back(abc, dot(abc, a)); // {2 乗, 3 乗} オーダー
    };

    for(ll i = 0; i < A.size(); i++) if(g[i].size()){
        // ある頂点から出る 1 つの辺を軸として偏角ソート
        auto& adj = g[i];
        const ll j = adj[0];
        const Point V = A[j] - A[i];
        sort(adj.begin() + 1, adj.end(), [&](ll j, ll k){ return dot(cross(A[j] - A[i], A[k] - A[i]), V) > 0; });
        const ll n = adj.size();
        adj.push_back(j);
        // 隣り合う 2 本の辺で面を作る
        for(ll j = 0; j < n; j++) add_plane(A[i], A[adj[j]], A[adj[j + 1]]);
    }
    sort(planes.begin(), planes.end());
    uniq(planes);

    // 全ての点が 1 平面上にある場合
    if(planes.size() == 1){
        vector<Point> poly;
        ll u = edge[0].first, v = edge[0].second;
        while(poly.size() < edge.size()){
            poly.push_back(A[u]);
            tie(u, v) = pair{v, u ^ g[v][0] ^ g[v][1]};
        }
        return solve2D(poly, K);
    }

    // 面のどちら側にあるか (dot(V, x) ≤ C)
    for(auto& [V, C] : planes){
        for(Point x : A){
            ll d = dot(V, x);
            if(d == C) continue;
            if(d > C){
                V = -V;
                C = -C;
            }
            break;
        }
    }

    // z 座標で切って、半平面の積
    remove_if(planes, [](pair<Point, ll> pl){ auto [V, C] = pl; return V.x == 0 && V.y == 0; });
    // x, y のみで偏角ソート
    sort(planes.begin(), planes.end(), [](pair<Point, ll> a, pair<Point, ll> b){
        auto [V1, C1] = a;
        auto [V2, C2] = b;
        const bool u1 = pair{V1.x, V1.y} < pair{0LL, 0LL};
        const bool u2 = pair{V2.x, V2.y} < pair{0LL, 0LL};
        if(u1 != u2) return u1 < u2;
        return V1.x * V2.y - V1.y * V2.x > 0; // 4 乗
    });

    // 格子点を数える
    ll zmin = INT_MAX, zmax = INT_MIN;
    for(auto [x, y, z] : A){
        chmin(zmin, z);
        chmax(zmax, z);
    }
    auto count_lattice = [&]() -> Modint {
        Modint ans = 0;
        for(ll z = zmin; z <= zmax; z++){
            vector<array<ll, 3>> lines; // Ax + By <= C
            for(auto& [V, C] : planes){
                // 傾きを unique
                array<ll, 3> l = {V.x, V.y, C - V.z * z};
                if(lines.size()){
                    auto& [a, b, c] = lines.back();
                    if(a * l[1] == b * l[0]){
                        if(c * (abs(l[0]) + abs(l[1])) > l[2] * (abs(a) + abs(b))){ // 5 乗
                            lines.back() = l;
                        }
                        continue;
                    }
                }
                lines.push_back(l);
            }

            // いらない半平面を捨てる
            auto is_needed = [](array<ll, 3> l0, array<ll, 3> l, array<ll, 3> l1) -> bool {
                const __int128_t W = l0[0] * l1[1] - l0[1] * l1[0]; // W 倍して整数点に (4 乗オーダー)
                if(W <= 0) return 1; // 角 l0 - l1 が 180 度以上
                // l0, l1 の交点
                array<__int128_t, 2> p01 = {l0[2] * l1[1] - l0[1] * l1[2], l0[0] * l1[2] - l0[2] * l1[0]}; // 5 乗オーダー
                return l[0] * p01[0] + l[1] * p01[1] > l[2] * W; // 7 乗オーダー
            };
            vector<array<ll, 3>> poly;
            for(auto& l : lines){
                while(poly.size() >= 2 && !is_needed(poly.rbegin()[1], poly.back(), l)) poly.pop_back();
                poly.push_back(l);
            }
            {
                auto p = poly.begin(), q = prev(poly.end());
                while(q - p > 2){
                    if(!is_needed(*q, *p, *next(p))){
                        p++;
                        continue;
                    }
                    if(!is_needed(*prev(q), *q, *p)){
                        q--;
                        continue;
                    }
                    break;
                }
                poly = vector(p, next(q));
            }

            // y 座標で切る
            ll ymin = INT_MAX, ymax = INT_MIN;
            auto calc_y = [&](array<ll, 3> l0, array<ll, 3> l1){
                const ll p = l0[0] * l1[2] - l0[2] * l1[0], q = l0[0] * l1[1] - l0[1] * l1[0]; // 5 乗, 4 乗
                chmin(ymin, div_ceil(p, q));
                chmax(ymax, div_floor(p, q));
            };
            for(ll i = 0; i + 1 < poly.size(); i++) calc_y(poly[i], poly[i + 1]);
            calc_y(poly.back(), poly.front());
            assert(ymin <= ymax + 1);

            remove_if(poly, [](array<ll, 3> l){ return l[0] == 0; });
            auto mid = partition_point(poly.begin(), poly.end(), [](array<ll, 3> l){ return l[0] > 0; });
            vector<array<ll, 3>> lower(poly.rbegin(), reverse_iterator{mid}), upper(poly.begin(), mid);
            auto lo = lower.begin(), hi = upper.begin();
            for(ll y = ymin; y <= ymax; y++){
                // しゃくとりで x 座標の範囲を計算
                auto cross_y = [&](array<ll, 3> l0, array<ll, 3> l1){
                    const ll p = l0[0] * l1[2] - l0[2] * l1[0], q = l0[0] * l1[1] - l0[1] * l1[0]; // 5 乗, 4 乗
                    return div_ceil(p, q);
                };
                while(next(lo) != lower.end() && cross_y(*next(lo), *lo) <= y) lo++;
                while(next(hi) != upper.end() && cross_y(*hi, *next(hi)) <= y) hi++;
                auto calc_x_lo = [&](array<ll, 3> l){ return div_ceil(y * l[1] - l[2], -l[0]); };
                auto calc_x_hi = [&](array<ll, 3> l){ return div_floor(l[2] - y * l[1], l[0]); };
                ll xmin = calc_x_lo(*lo);
                ll xmax = calc_x_hi(*hi);
                assert(xmin <= xmax + 1);
                ans += xmax - xmin + 1;
            }
        }
        return ans;
    };

    // エルハート多項式を補間
    const Modint a = count_lattice() - 1;
    for(auto& [V, C] : planes) C *= 2;
    zmin *= 2;
    zmax *= 2;
    const Modint b = count_lattice() - 1;
    for(auto& [V, C] : planes){
        C /= 2;
        C *= 3;
    }
    zmin /= 2;
    zmax /= 2;
    zmin *= 3;
    zmax *= 3;
    const Modint c = count_lattice() - 1;
    vector<Modint> ehrhart = {1, (18 * a - 9 * b + 2 * c) / 6, (-15 * a + 12 * b - 3 * c) / 6, (3 * a - 3 * b + 1 * c) / 6};

    return ((ehrhart[3] * K + ehrhart[2]) * K + ehrhart[1]) * K + ehrhart[0];
}
int main(){
    ll N, K;
    cin >> N >> K;
    vector<Point> A(N);
    for(auto& [x, y, z] : A) cin >> x >> y >> z;
    cout << solve(move(A), K).val() << endl;
}

Submission Info

Submission Time
Task N - Expanded Hull
User tatyam
Language C++ (GCC 9.2.1)
Score 500
Code Size 13374 Byte
Status AC
Exec Time 119 ms
Memory 3716 KiB

Compile Error

./Main.cpp: In function ‘Modint solve(std::vector<Point>, Modint)’:
./Main.cpp:195:21: warning: comparison of integer expressions of different signedness: ‘ll’ {aka ‘long long int’} and ‘std::vector<Point>::size_type’ {aka ‘long unsigned int’} [-Wsign-compare]
  195 |     for(ll i = 0; i < A.size(); i++) if(g[i].size()){
      |                   ~~^~~~~~~~~~
./Main.cpp: In lambda function:
./Main.cpp:306:33: warning: comparison of integer expressions of different signedness: ‘ll’ {aka ‘long long int’} and ‘std::vector<std::array<long long int, 3> >::size_type’ {aka ‘long unsigned int’} [-Wsign-compare]
  306 |             for(ll i = 0; i + 1 < poly.size(); i++) calc_y(poly[i], poly[i + 1]);
      |                           ~~~~~~^~~~~~~~~~~~~

Judge Result

Set Name Sample All
Score / Max Score 0 / 0 500 / 500
Status
AC × 3
AC × 40
Set Name Test Cases
Sample sample-01.txt, sample-02.txt, sample-03.txt
All almost-line-01.txt, almost-line-02.txt, almost-plane-01.txt, almost-plane-02.txt, big-01.txt, big-02.txt, big-03.txt, big-04.txt, big-05.txt, big-06.txt, bipyramid-01.txt, bipyramid-02.txt, bipyramid-03.txt, first-dilation-01.txt, first-dilation-02.txt, first-dilation-03.txt, first-dilation-04.txt, first-dilation-05.txt, first-dilation-06.txt, first-dilation-07.txt, first-dilation-08.txt, first-dilation-09.txt, first-dilation-10.txt, first-dilation-11.txt, first-dilation-12.txt, little-vertices-01.txt, little-vertices-02.txt, little-vertices-03.txt, many-facets-01.txt, many-facets-02.txt, many-facets-03.txt, many-facets-04.txt, many-facets-05.txt, many-vertices-01.txt, many-vertices-02.txt, many-vertices-03.txt, many-vertices-04.txt, sample-01.txt, sample-02.txt, sample-03.txt
Case Name Status Exec Time Memory
almost-line-01.txt AC 13 ms 3444 KiB
almost-line-02.txt AC 19 ms 3548 KiB
almost-plane-01.txt AC 68 ms 3616 KiB
almost-plane-02.txt AC 83 ms 3648 KiB
big-01.txt AC 54 ms 3500 KiB
big-02.txt AC 53 ms 3504 KiB
big-03.txt AC 53 ms 3512 KiB
big-04.txt AC 63 ms 3616 KiB
big-05.txt AC 65 ms 3512 KiB
big-06.txt AC 67 ms 3460 KiB
bipyramid-01.txt AC 71 ms 3568 KiB
bipyramid-02.txt AC 70 ms 3640 KiB
bipyramid-03.txt AC 72 ms 3556 KiB
first-dilation-01.txt AC 61 ms 3512 KiB
first-dilation-02.txt AC 107 ms 3548 KiB
first-dilation-03.txt AC 88 ms 3524 KiB
first-dilation-04.txt AC 84 ms 3608 KiB
first-dilation-05.txt AC 61 ms 3456 KiB
first-dilation-06.txt AC 101 ms 3576 KiB
first-dilation-07.txt AC 75 ms 3468 KiB
first-dilation-08.txt AC 85 ms 3540 KiB
first-dilation-09.txt AC 80 ms 3568 KiB
first-dilation-10.txt AC 77 ms 3580 KiB
first-dilation-11.txt AC 78 ms 3536 KiB
first-dilation-12.txt AC 88 ms 3648 KiB
little-vertices-01.txt AC 17 ms 3496 KiB
little-vertices-02.txt AC 25 ms 3600 KiB
little-vertices-03.txt AC 25 ms 3500 KiB
many-facets-01.txt AC 117 ms 3716 KiB
many-facets-02.txt AC 111 ms 3516 KiB
many-facets-03.txt AC 109 ms 3624 KiB
many-facets-04.txt AC 110 ms 3516 KiB
many-facets-05.txt AC 110 ms 3520 KiB
many-vertices-01.txt AC 117 ms 3576 KiB
many-vertices-02.txt AC 113 ms 3520 KiB
many-vertices-03.txt AC 114 ms 3632 KiB
many-vertices-04.txt AC 119 ms 3480 KiB
sample-01.txt AC 2 ms 3500 KiB
sample-02.txt AC 2 ms 3560 KiB
sample-03.txt AC 2 ms 3500 KiB