Submission #1161184

#TimeUsernameProblemLanguageResultExecution timeMemory
1161184wanglingyun9Walk (CEOI06_walk)C++17
40 / 100
124 ms38248 KiB
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cassert>
using namespace std;
 
// ----- Structures for Buildings, Candidates, and Events -----
 
struct Building {
    int r;      // right coordinate = max(x1,x2)
    int lower;  // min(y1,y2)
    int upper;  // max(y1,y2)
    int id;     // building id (0-indexed)
};
 
// Candidate point: either the upper-right (TU) or lower-right (TD) corner of a building,
// or the dummy candidate for the destination.
struct Candidate {
    int x, y;   // grid coordinates
    int type;   // 0: TU, 1: TD, 2: dummy (destination)
    int bID;    // building id (for TU and TD); -1 for dummy
    int nxt;    // the "next" building id (computed by sweep–line); -1 means ε.
    // DP fields:
    long long dp;   // best (minimum) Manhattan distance from S=(0,0) to this candidate along a special path.
    int parent;     // parent candidate index (for path reconstruction), or -1 if coming directly from S.
};
 
// An event for the sweep–line (to compute "next" for candidates).
// There are four kinds:
//   type 0: Activation of building at y = building.lower
//   type 1: Deactivation of building at y = building.upper
//   type 2: Query for TD candidate (at y = candidate.y, which equals building.lower - 1)
//   type 3: Query for TU candidate (at y = candidate.y, which equals building.upper + 1)
// (For the dummy candidate we treat it as a TU candidate.)
struct Event {
    int y;
    int type;      // 0: activate, 1: deactivate, 2: queryTD, 3: queryTU
    int x;         // the x coordinate at which we update/query (this is the building's right side)
    int bID;       // building id (for activate/deactivate events)
    int candIdx;   // candidate index (for query events)
};
 
// ----- Segment Tree (Tournament Tree) for range maximum query -----
// We store a pair: (val, bID) where val is the x coordinate (i.e. building right coordinate)
// and bID is the building id. The merge returns the pair with larger 'val' (i.e. the right–most active building).
 
struct SegNode {
    int val;
    int bID;
};
 
struct SegmentTree {
    int size;
    vector<SegNode> tree;
    SegmentTree(int n) {
        size = 1;
        while(size < n) size *= 2;
        tree.assign(2 * size, {-1, -1});
    }
    // Update position pos (0-indexed) to value (val, bID)
    void update(int pos, int val, int bID) {
        pos += size;
        tree[pos] = {val, bID};
        for(pos /= 2; pos >= 1; pos /= 2) {
            if(tree[2*pos].val >= tree[2*pos+1].val)
                tree[pos] = tree[2*pos];
            else
                tree[pos] = tree[2*pos+1];
        }
    }
    // Query the maximum in interval [l, r] (inclusive)
    SegNode query(int l, int r) {
        SegNode res = {-1, -1};
        l += size; r += size;
        while(l <= r) {
            if(l % 2 == 1) {
                if(tree[l].val > res.val) res = tree[l];
                l++;
            }
            if(r % 2 == 0) {
                if(tree[r].val > res.val) res = tree[r];
                r--;
            }
            l /= 2; r /= 2;
        }
        return res;
    }
};
 
// ----- Helper: Manhattan distance between two points -----
 
inline long long manhattan(int x1, int y1, int x2, int y2) {
    return llabs(x1 - x2) + llabs(y1 - y2);
}
 
// ----- Main -----
 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
 
    int X, Y, N;
    cin >> X >> Y;
    cin >> N;
    vector<Building> buildings;
    buildings.reserve(N);
    for (int i = 0; i < N; i++){
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        int r = max(x1, x2);
        int lower = min(y1, y2);
        int upper = max(y1, y2);
        buildings.push_back({r, lower, upper, i});
    }
 
    // ----- Build candidate points -----
    // For each building, we create two candidates:
    //   TU: (r, upper + 1)
    //   TD: (r, lower - 1)
    // And we also add one dummy candidate for the destination D = (X, Y).
    vector<Candidate> cand;
    cand.reserve(2 * N + 1);
    // We will also remember, for each building, the candidate indices for TU and TD.
    vector<int> candTU(N, -1), candTD(N, -1);
    for (int i = 0; i < N; i++){
        // Upper–right candidate (TU)
        Candidate c;
        c.x = buildings[i].r;
        c.y = buildings[i].upper + 1;
        c.type = 0;  // TU
        c.bID = buildings[i].id;
        c.nxt = -1;
        cand.push_back(c);
        candTU[buildings[i].id] = cand.size() - 1;
 
        // Lower–right candidate (TD)
        c.x = buildings[i].r;
        c.y = buildings[i].lower - 1;
        c.type = 1;  // TD
        c.bID = buildings[i].id;
        c.nxt = -1;
        cand.push_back(c);
        candTD[buildings[i].id] = cand.size() - 1;
    }
    // Add dummy candidate for destination D; we treat it as a TU candidate.
    Candidate dummy;
    dummy.x = X;
    dummy.y = Y;
    dummy.type = 2; // dummy
    dummy.bID = -1;
    dummy.nxt = -1;
    cand.push_back(dummy);
    int dummyIdx = cand.size() - 1;
 
    // ----- Build events for the sweep–line (to compute candidate.nxt) -----
    // For each building, add:
    //   Activation event at y = lower, type 0.
    //   Deactivation event at y = upper, type 1.
    // For each candidate from a building:
    //   For TD (type 1): add query event at y = (lower - 1) [i.e. candidate.y], type 2.
    //   For TU (type 0): add query event at y = (upper + 1) [i.e. candidate.y], type 3.
    // For the dummy candidate (type 2), add query event at y = Y + 1, type 3.
    vector<Event> events;
    events.reserve(2 * N + cand.size());
    for (int i = 0; i < N; i++){
        // Activation event for building i at y = lower
        Event ev;
        ev.y = buildings[i].lower;
        ev.type = 0; // activate
        ev.x = buildings[i].r;
        ev.bID = buildings[i].id;
        ev.candIdx = -1;
        events.push_back(ev);
 
        // Deactivation event for building i at y = upper
        ev.y = buildings[i].upper;
        ev.type = 1; // deactivate
        ev.x = buildings[i].r;
        ev.bID = buildings[i].id;
        ev.candIdx = -1;
        events.push_back(ev);
    }
    // Candidate query events:
    for (int i = 0; i < (int)cand.size(); i++){
        Event ev;
        // For candidates from buildings:
        if(cand[i].type == 0){ // TU candidate: query event type 3 at y = candidate.y (== upper+1)
            ev.y = cand[i].y;
            ev.type = 3; // query TU
        } else if(cand[i].type == 1){ // TD candidate: query event type 2 at y = candidate.y (== lower-1)
            ev.y = cand[i].y;
            ev.type = 2; // query TD
        } else if(cand[i].type == 2){
            // For dummy candidate, treat as TU; add query event at y = Y+1.
            ev.y = Y + 1;
            ev.type = 3; // query TU
        }
        ev.x = cand[i].x; // candidate's x coordinate
        ev.candIdx = i;
        ev.bID = -1;
        events.push_back(ev);
    }
    // Sort events by y; if equal, by type (we assume lower type values come first)
    sort(events.begin(), events.end(), [](const Event &a, const Event &b) {
        if(a.y == b.y) return a.type < b.type;
        return a.y < b.y;
    });
 
    // ----- Process events with a segment tree -----
    // Domain for x: x can be from 0 up to max (we assume maxX = 1000001)
    const int MAXX = 1000002;
    SegmentTree seg(MAXX);
    // Initially, all positions are -1.
    for (int i = 0; i < (int)events.size(); i++){
        if(events[i].type == 0) { // Activation: add building at position events[i].x
            seg.update(events[i].x, events[i].x, events[i].bID);
        } else if(events[i].type == 1) { // Deactivation: remove building at position events[i].x
            seg.update(events[i].x, -1, -1);
        } else if(events[i].type == 2 || events[i].type == 3) { // Query events for candidate.
            SegNode res = seg.query(0, events[i].x);
            int found = res.val; // if -1, then no building found.
            int foundBID = res.bID;
            cand[events[i].candIdx].nxt = foundBID; // will be -1 if not found.
        }
    }
 
    // ----- DP: compute shortest distance (and parent pointers) for each candidate.
    // Our recurrence is:
    //   If cand.nxt == -1, then dp = cand.x + |cand.y| (distance from S = (0,0)).
    //   Otherwise, let r = cand.nxt (a building id) and let P1 = candidate TU for building r,
    //             P2 = candidate TD for building r.
    //         Then: dp = min( dp[P1] + d(P1, cand), dp[P2] + d(P2, cand) ).
    // Since r < cand.x, these candidates come earlier when we sort by x.
 
    // Sort candidates by (x, then y)
    vector<int> order(cand.size());
    for (int i = 0; i < (int)cand.size(); i++)
        order[i] = i;
    sort(order.begin(), order.end(), [&](int a, int b) {
        if(cand[a].x == cand[b].x)
            return cand[a].y < cand[b].y;
        return cand[a].x < cand[b].x;
    });
 
    const long long INF = 1LL << 60;
    vector<long long> dp(cand.size(), INF);
    vector<int> par(cand.size(), -1);
 
    // For quick lookup: for each building id, record the candidate indices for TU and TD.
    // (They were computed earlier in candTU and candTD.)
 
    for (int idx : order) {
        // Base case: if no building is encountered when going left from this candidate.
        if(cand[idx].nxt == -1) {
            dp[idx] = (long long)cand[idx].x + llabs(cand[idx].y);
            par[idx] = -1;
        } else {
            int r = cand[idx].nxt; // building id from "next"
            // Use the two candidate points for building r.
            int idxTU = candTU[r];
            int idxTD = candTD[r];
            // They must have been processed already (since their x < cand[idx].x)
            long long opt1 = dp[idxTU] + manhattan(cand[idx].x, cand[idx].y, cand[idxTU].x, cand[idxTU].y);
            long long opt2 = dp[idxTD] + manhattan(cand[idx].x, cand[idx].y, cand[idxTD].x, cand[idxTD].y);
            if(opt1 <= opt2) {
                dp[idx] = opt1;
                par[idx] = idxTU;
            } else {
                dp[idx] = opt2;
                par[idx] = idxTD;
            }
        }
    }
 
    // Find the dummy candidate (destination)
    int destIdx = -1;
    for (int i = 0; i < (int)cand.size(); i++){
        if(cand[i].type == 2) {
            destIdx = i;
            break;
        }
    }
    assert(destIdx != -1);
 
    // The answer (minimum path length) is dp[destIdx]
    long long ans = dp[destIdx];
 
    // ----- Reconstruct the candidate chain (turning–points) -----
    vector<int> chain;
    int cur = destIdx;
    while(cur != -1) {
        chain.push_back(cur);
        cur = par[cur];
    }
    // Reverse the chain so that it goes from the candidate reached directly from S up to destination.
    reverse(chain.begin(), chain.end());
 
    // Build the polyline: start with S = (0,0) then the candidate points in order.
    vector<pair<int,int>> polyline;
    polyline.push_back({0, 0});
    for (int idx : chain)
        polyline.push_back({cand[idx].x, cand[idx].y});
 
    // ----- Compress the polyline into alternating horizontal and vertical segments -----
    // (Merge consecutive segments that are collinear.)
    vector<pair<int,int>> points;
    points.push_back(polyline[0]);
    for (int i = 1; i < (int)polyline.size(); i++){
        // If the last two points and the new point are collinear (in a horizontal or vertical direction), merge.
        if(points.size() >= 2) {
            auto &p1 = points[points.size()-2];
            auto &p2 = points[points.size()-1];
            auto &p3 = polyline[i];
            // Check if p1->p2 and p2->p3 are parallel
            if((p2.first - p1.first == 0 && p3.first - p2.first == 0) ||
               (p2.second - p1.second == 0 && p3.second - p2.second == 0)) {
                points.back() = p3;
                continue;
            }
        }
        points.push_back(polyline[i]);
    }
 
    // Now, segments are differences between consecutive points in 'points'
    vector<pair<int,int>> segments;
    for (int i = 0; i < (int)points.size()-1; i++){
        int dx = points[i+1].first - points[i].first;
        int dy = points[i+1].second - points[i].second;
        segments.push_back({dx, dy});
    }
 
    // ----- Output the solution -----
    cout << ans << "\n";
    cout << segments.size() << "\n";
    for (auto &seg : segments)
        cout << seg.first << " " << seg.second << "\n";
 
    return 0;
}
#Verdict Execution timeMemoryGrader output
Fetching results...