Official

F - Takahashi on Grid Editorial by en_translator


First, we can swap them as needed to assume that \(s _ {x,i}\leq t _ {x,i}\).

Takahashi can minimize his moves by choosing the route as follows:

  • Let \((x,y)=(s _ {x,i},s _ {y,i})\) be his initial square.
  • While \(x\lt t _ {x,i}\), repeat the following:
    • If \(y\lt L _ {x+1}\), move to square \((x,y+1)\).
    • If \(L _ {x+1}\leq y\leq U _ {x+1}\), move to square \((x+1,y)\).
    • If \(U _ {x+1}\lt y\), move to square \((x,y-1)\).
  • While \(y\neq t _ {y,i}\), move to the appropriate position.

This can be justified because making a move toward \(x\) direction as early as possible does not worsen the count, though we do not describe the detailed proof.

Under this algorithm, consider his move from \(x=A-1\) to \(x=B\). For \(y\in\mathbb Z\), when starting the algorithm above from the state where he is at square \((A-1,y)\), let \((B,f _ {\lbrack A,B\rbrack}(y))\) be the first square to reach \(x=B\), and \(g _ {\lbrack A,B\rbrack}(y)\) be the number of moves required. (For convenience, we assume \((A-1,y)\) are all empty.)

Then, there exist \(f=\lbrack f _ L,f _ U\rbrack,g=\lbrack g _ L,g _ U\rbrack\), and \(C\), such that

  • \(f _ {\lbrack{A,B}\rbrack}(y)=\operatorname{clamp}(y,f _ L,f _ U)\),
  • \(g _ {\lbrack{A,B}\rbrack}(y)=C+\max\lbrace0,g _ L-y,y-g _ U\rbrace\),
  • \(f _ L\neq f _ U\vee g _ L\neq g _ U\implies f=g\).

(Here, \(\operatorname{clamp}(x,a,b)\coloneqq\min\lbrace\max\lbrace x,a\rbrace,b\rbrace\).)

Proof

We proof by induction on the width of the segment \(\lbrack A,B\rbrack\).

If \(A=B\), the conditions are met by setting \(f=g=\lbrack L _ B,U _ B\rbrack,C=1\).

If \(A\lt B\), we have \[f _ {\lbrack{A,B}\rbrack}(y)=\operatorname{clamp}(f _ {\lbrack{A,B-1}\rbrack}(y),L _ B,U _ B)\] and \[g _ {\lbrack{A,B}\rbrack}(y)=1+g _ {\lbrack{A,B-1}\rbrack}(y)+\max\lbrace{0,L _ B-f _ {\lbrack{A,B-1}\rbrack}(y),f _ {\lbrack{A,B-1}\rbrack}(y)-U _ B}\rbrace.\]

By the assumption of the induction, we can take segments \(f\) and \(g\) and an integer \(C\) that satisfy the conditions for \(f _ {\lbrack{A,B-1}\rbrack},g _ {\lbrack{A,B-1}\rbrack}\).

Since the \(\operatorname{clamp}\) function is closed under composition, there exists \(\bar f=\lbrack\bar f _ L,\bar f _ U\rbrack\) such that \(f _ {\lbrack{A,B}\rbrack}(y)=\operatorname{clamp}(y,\bar f_L,\bar f _ U)\).

If \(\bar f _ L\neq\bar f _ U\), then \(\bar f=f\cap\lbrack L _ B,U _ B\rbrack\) holds; since \(f _ L\neq f _ U\) is necessary, \(f=g\) holds. Comparing \(y\) with \(f _ L,f _ U,L _ B,U _ B\), it turns out that \(g _ {\lbrack{A,B}\rbrack}(y)=C+1+\max\lbrace0,\bar f _ L-y,y-\bar f _ U\rbrace\).

If \(\bar f _ L=\bar f _ U\) but \(f _ L\neq f _ U\), then \(f\cap\lbrack L _ B,U _ B\rbrack=\emptyset\). Comparing \(f\) with \(\lbrack L _ B,U _ B\rbrack\), one can take either \(g _ L\) or \(g _ U\) as \(\bar g\) to have \(g _ {\lbrack{A,B}\rbrack}(y)=\bar C+|y-\bar g|\).

If \(f _ L=f _ U\), then \(f _ {\lbrack{A,B-1}\rbrack}\) is a constant function, thus \(g _ {\lbrack{A,B}\rbrack}\) equals \(g _ {\lbrack{A,B-1}\rbrack}\) plus some constant. Thus it has been proved.

Consider corresponding each segment \(\lbrack{A,B}\rbrack\) with \(f,g,C\) that give \(f _ {\lbrack{A,B}\rbrack},g _ {\lbrack{A,B}\rbrack}\).

If one can, given \((f _ 1,g _ 1,C _ 1)\) and \((f _ 2,g _ 2,C _ 2)\) for some adjacent segments, find \((f,g,C)\) for the joined segment, then this problem can be solved with a data structure like segment tree.

Bonus: when can an operation be processed on a segment tree?

That “a segment tree can process monoidal structure” does not explain the fact that a segment tree can treat an operation corresponding to merging adjacent segments. However, in general an operation corresponding to merging adjacent segments can be handled with a segment tree. (A famous formalization employs the category theory.)

In our case, we do not use the property that operation acts on adjacent segments, so composition can be computed for any \((f _ 1,g _ 1,C _ 1)\) and \((f _ 2,g _ 2,C _ 2)\). Since this operation forms a monoid, one can assert that it can be processed with a segment tree under the formalization using monoid.

Specifically, for \(f _ 1=[f _ {L,1},f _ {U,1}],g _ 1=[g _ {L,1},g _ {U,1}],f _ 2=[f _ {L,2},f _ {U,2}]\), and \(g _ 2=[g _ {L,2},g _ {U,2}]\), the composition can be computed by \[f=[\operatorname{clamp}(f _ {L,1},f _ {L,2},f _ {U,2}),\operatorname{clamp}(f _ {U,1},f _ {L,2},f _ {U,2})]\] and \[g=[\operatorname{clamp}(g _ {L,2},g _ {L,1},g _ {U,1}),\operatorname{clamp}(g _ {U,2},g _ {L,1},g _ {U,1})].\] \(C\) can be found as the number of moves for \(y=g _ L\).

Therefore, merging segments can be done in an \(O(1)\) time.

Since the sequence is not updated, one can use an even faster data structure, but the \(O(N+Q\log N)\) solution using a segment tree is already fast enough.

The following is sample code.

#include <iostream>
#include <vector>
#include <algorithm>
#include <atcoder/segtree>

int main() {
    using namespace std;
    // Structure that manages the cost when entering and exiting the segment [A, B] on x
    // Given the y coordinate on entrance, returns (y coordinate on exit, number of moves).
    struct block{
        unsigned fL, fU;
        unsigned gL, gU;
        unsigned long C;
        pair<unsigned, unsigned long> operator()(unsigned y) const {
            return {clamp(y, fL, fU), C + max(y, gL) - min(y, gU)};
        }
    };
    // Definition of the segment tree
    // The binary operation is merging segments
    using segment_tree = atcoder::segtree<block, [](const block& lhs, const block& rhs) -> block {
        // Rather confusing merge operation
        const auto new_fL{clamp(lhs.fL, rhs.fL, rhs.fU)}, new_fU{clamp(lhs.fU, rhs.fL, rhs.fU)};
        const auto new_gL{clamp(rhs.gL, lhs.gL, lhs.gU)}, new_gU{clamp(rhs.gU, lhs.gL, lhs.gU)};
        const auto new_C{lhs.C + rhs(lhs(new_gU).first).second};
        return {new_fL, new_fU, new_gL, new_gU, new_C};
    }, []{
        // Unit operation opens up everything
        return block{0, 1000000001, 0, 1000000001, 0};
    }>;

    unsigned N;
    cin >> N;
    vector<block> intervals(N);
    for(auto&& [fL, fU, gL, gU, C] : intervals){
        unsigned L, U;
        cin >> L >> U;
        // Construct a value representing a column with [L, U] being empty
        fL = gL = L;
        fU = gU = U;
        C = 1;
    }
    segment_tree seg_tree{intervals};

    unsigned Q;
    cin >> Q;
    for(unsigned i{}; i < Q; ++i){
        unsigned sx, sy, tx, ty;
        cin >> sx >> sy >> tx >> ty;
        if (sx > tx){
            swap(sx, tx);
            swap(sy, ty);
        }
        // When entering [sx, tx] from sy, on which y coordinate it exists with how many moves?
        const auto& [out, cost]{seg_tree.prod(sx, tx)(sy)};
        // Adjust the y coordinate and print it
        cout << max(out, ty) - min(out, ty) + cost << endl;
    }

    return 0;
}

posted:
last update: