Official

F - Rooks Editorial by Errichto


If you want a short editorial, scroll down to Full Problem section.

Hints/Subtasks

Here’s an unusual hint: just try to solve these three easier versions of a problem:

P1. \(N \leq 3000\)
P2. Same as full problem but the input is monotonic (\(x[i] < x[i+1]\), \(y[i] < y[i+1]\)).
P3. Same as full problem but you only need to consider the scenario of replacing rook 1.

P1. \(N \leq 3000\)

Let’s assume that rooks are sorted by \(x\)-coordinate. At any moment, the rooks beaten so far form an interval of indices \([L, R]\) and our last move was to beat rook \(L\) or rook \(R\). The next move must be to beat rook \(L-1\) or rook \(R+1\) and that’s allowed only if such a next rook isn’t blocked by another rook that has \(y\)-coordinate closer to our current position. In order to check that in \(O(1)\), let’s also maintain the current interval of available \(y\)-coordinates (you can compress \(y\)’s first).

For fixed starting position, let \(dp[L][R][isLeft]\) denote minimum time to beat interval of rooks \([L, R]\) and finish at left/right end of this interval. We transition from \(dp[L][R][0/1]\) to \(dp[L-1][R][0]\) and to \(dp[L][R+1][1]\). Running this dp for every starting position gives \(O(N^3)\) in total.

Even better, let \(dp[L][R][isLeft]\) denote the minimum remaining time to finish the process optimally if we are now at state \((L, R, isLeft)\). We can run this dp once and then \(dp[i][i][0/1]\) is the answer for \(i\)-th starting position. Time complexity is \(O(N^2)\).

P2. Monotonic Input

You need \(|x_1-x_2|+|y_1-y_2|-1\) steps to get from \((x_1, y_1)\) to rook at \((x_2, y_2)\). You can treat the distance between two rooks as the manhattan distance \(|x_1-x_2|+|y_1-y_2|\) and just subtract \(countBeatenRooks\) when printing.

Let \(dist(i, j)\) denote manhattan distance between rooks \(i\) and \(j\).

Lemma: \(dist(a, b) + dist(b, c) = dist(a, c)\) for \(a < b < c\).

This implies that when we go e.g. from rook \(4\) to rook \(8\), we can just go through positions of already-beaten rooks \(5, 6, 7\). Hence:

Important Lemma: starting from rook \(s\), there are two possible optimal paths: \(s \rightarrow 1 \rightarrow n\) or \(s \rightarrow n \rightarrow 1\).
(That is, just keep going left and then keep going right, or the other way around.)

If \(x\)’s and \(y\)’s are sorted in the input, we have

\[answer[i] = min(dist(i, 1) + dist(1, n), dist(i, n) + dist(n, 1))\]

This property wouldn’t hold if the king could move in all 8 directions.

P3. One Starting Position & Full Problem

See the video analysis here https://www.youtube.com/watch?v=SN0dyP2kJgo&t=22m

The solution will be later written down here as well.

code
#include  // Rooks, by Errichto
using namespace std;
#define sim template < class c
#define ris return * this
#define dor > debug & operator <<
#define eni(x) sim > typename \
  enable_if(0) x 1, debug&>::type operator<<(c i) {
sim > struct rge { c b, e; };
sim > rge range(c i, c j) { return rge{i, j}; }
sim > auto dud(c* x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
#ifdef LOCAL
~debug() { cerr << endl; }
eni(!=) cerr << boolalpha << i; ris; }
eni(==) ris << range(begin(i), end(i)); }
sim, class b dor(pair < b, c > d) {
  ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge d) {
  *this << "[";
  for (auto it = d.b; it != d.e; ++it)
  *this << ", " + 2 * (it == d.b) << *it;
  ris << "]";
}
#else
sim dor(const c&) { ris; }
#endif
};
#define imie(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
// debug & operator << (debug & dd, P p) { dd << "(" << p.x << ", " << p.y << ")"; return dd; }

struct Rook {
  int x, y, id;
  bool operator <(const Rook& other) const {
    return x < other.x;
  }
};
long long hack;
int main() {
  int n;
  scanf("%d", &n);
  assert(2 <= n && n <= 200 * 1000);
  vector rooks(n);
  for(int i = 0; i < n; ++i) {
    rooks[i].id = i;
    scanf("%d%d", &rooks[i].x, &rooks[i].y);
    assert(1 <= min(rooks[i].x, rooks[i].y) && max(rooks[i].x, rooks[i].y) <= 1000 * 1000);
  }
  sort(rooks.begin(), rooks.end());
  vector ys{INT_MIN, INT_MAX}; // two sentinels for convenience
  for(int i = 0; i < n; ++i) {
    ys.push_back(rooks[i].y);
  }
  sort(ys.begin(), ys.end());
  for(int i = 0; i < n - 1; ++i) {
    assert(ys[i] != ys[i+1]);
    assert(rooks[i].x != rooks[i+1].x);
  }
  vector> answer(n, {-1, -1});
  for(int start = 0; start < n; ++start) {
    if(answer[rooks[start].id].first != -1) {
      continue;
    }
    
    int start_y = lower_bound(ys.begin(), ys.end(), rooks[start].y) - ys.begin();
    int y1 = start_y - 1, y2 = start_y + 1;
    
    int L = start, R = start;
    int prev_dir = -1;
    vector> intervals;
    while(true) {
      bool found = false;
      for(int i : {L - 1, R + 1}) {
        if(0 <= i && i < n) {
          bool prev_y = (ys[y1] == rooks[i].y);
          bool next_y = (ys[y2] == rooks[i].y);
          if(prev_y || next_y) {
            int direction = (prev_y == (i == L - 1)); // 0 or 1
            // debug() << imie(start) imie(i) imie(direction);
            if(prev_dir == -1) {
              prev_dir = direction;
            }
            if(prev_dir != direction) {
              intervals.emplace_back(L, R);
              prev_dir = direction;
            }
            prev_y ? y1-- : y2++;
            i == L-1 ? L-- : R++;
            found = true;
            break;
          }
        }
      }
      if(!found) {
        break;
      }
    }
    if(intervals.empty() || intervals.back() != make_pair(L, R)) {
      intervals.emplace_back(L, R);
    }
    const int s = intervals.size();
    debug() << imie(start) imie(intervals);
    
    { // complexity check
      static int sum_s = 0, sum_len = 0;
      if(s > 1) sum_s += s;
      assert(sum_s <= n);
      sum_len += R - L + 1;
      assert(sum_len <= 3 * n); // N + out + 2 * border    ('out' and 'border' are disjoint)
      static vector basic(n), out(n), border(n);
      pair i_basic = intervals[0], i_out = intervals[max(0,s-2)], i_border = intervals.back();
      for(int i = i_border.first; i <= i_border.second; ++i) {
        if(i_basic.first <= i && i <= i_basic.second) {
          assert(++basic[i] == 1);
        }
        else if(i_out.first <= i && i <= i_out.second) {
          assert(++out[i] == 1 && border[i] == 0);
        }
        else {
          assert(++border[i] <= 2 && out[i] == 0);
        }
      }
    }
    
    const long long INF = 1e18L + 5;
    auto min_self = [&](long long& a, long long b) {
      a = min(a, b);
    };
    auto f = [&](int i, int j) { // manhattan distance between two rooks
      return abs(rooks[i].x - rooks[j].x) + abs(rooks[i].y - rooks[j].y);
    };
    vector> dp(s, vector(2, INF));
    // dp[i][0] is minimum number of REMAINING operations if we did interval i and we are at its left end
    // dp[i][1] -- same but right end
    dp[s-1][0] = dp[s-1][1] = 0;
    for(int who = s - 1; who >= 1; --who) {
      // [L1, [L2, R1], R2]
      int L1 = intervals[who].first;
      int L2 = intervals[who-1].first;
      int R1 = intervals[who-1].second;
      int R2 = intervals[who].second;
      hack += (long long) (L2 - L1) * (R2 - R1);
      min_self(dp[who-1][0], dp[who][0] + (R1 == R2 ? f(L1, L2) : f(L1, R2) + f(R2, L2)));
      min_self(dp[who-1][1], dp[who][0] + f(L1, R2) + f(R2, R1));
      min_self(dp[who-1][0], dp[who][1] + f(R2, L1) + f(L1, L2));
      min_self(dp[who-1][1], dp[who][1] + (L1 == L2 ? f(R1, R2) : f(R2, L1) + f(L1, R1)));
    }
    int low = intervals[0].first, high = intervals[0].second;
    for(int i = low; i <= high; ++i) {
      assert(answer[rooks[i].id] == make_pair(-1, -1LL));
      answer[rooks[i].id] = {
        R - L,
        min(dp[0][0] + f(low, high) + f(high, i), dp[0][1] + f(high, low) + f(low, i))
      };
    }
  }
  for(pair p : answer) {
    // printf("%d %lld\n", p.first, p.second - p.first);
    printf("%lld\n", p.second - p.first);
  }
  cerr << "hack = " << hack << endl;
}

posted:
last update: