MMA Contest 016 I: regisys?

解法

[1] グリッドの問題への言い換え

 i について、商品  i を購入できる一般人の人数を X _ i、商品  i を購入できる MMA 部員の人数を  Y _ i とします。また、一般人の人数に  1 を加えた値を  HMMA 部員の人数に  1 を加えた値を  W とおくと、以下のような問題に帰着されます。

 H \times W のグリッドがある。 N 個の点があり、点  i はマス  (X _ i, Y _ i) (0-indexed) にある。
 i = 1, 2, \dots, H について以下の操作をそれぞれ行う。

  •  x 座標が  i 以上の点を  1 つ選んで取り除く。(以下操作  x _ i と呼ぶ)

 i = 1, 2, \dots, W について以下の操作をそれぞれ行う。

  •  y 座標が  i 以上の点を  1 つ選んで取り除く。

残る点の最小個数を求めよ。(以下操作  y _ i と呼ぶ)

[2] Hall の結婚定理の利用

残る点の最小個数を求めるかわりに、最初にいくつかの点を消し、残った点の集合を  U としたとき、操作により  U に属する点をすべて取り除くことが可能であるために消す点の個数の最小値を求めることにします。

Hall の結婚定理により、 U に含まれる点をすべて取り除けることは、以下の条件と同値になります。

 U の任意の部分集合  U' について、 U' に属する点を取り除くことが可能な操作の集合を  \Gamma(U') とおくと、 |U'| \leq |\Gamma(U')| である。

 U' が空の場合は明らかに成立するので、以下  U' は空でないとします。 U' に属する点の  x 座標の最大値を  X _ {\text{max}} y 座標の最大値を  Y _ {\text{max}} とおくと、 \Gamma(U') = \{ x _ 1, x _ 2, \dots, x _ {X _ {\text{max}}}, y _ 1, y _ 2, \dots, y _ {Y _ {\text{max}}} \} となるので、 |\Gamma(U')| = X _ {\text{max}} + Y _ {\text{max}} です。よって |\Gamma(U')| X _ {\text{max}} Y _ {\text{max}} にしかよらないので、ある  X _ {\text{max}} Y _ {\text{max}} について  x \leq X _ {\text{max}}, y \leq Y _ {\text{max}} に含まれる点をすべて含むような部分集合  U' のみ考えればよいです。

したがって、 0 \leq i \leq H, 0 \leq j \leq W に対し、 x \leq i, y \leq j に含まれる点の個数を  A _ {ij} とおくと、点をすべて消せることは  A _ {ij} \leq i + j がすべての  i, j について成り立つことと同値になります。(点が存在しない  x または  y 座標についても考えていますが、点が存在するところまで  i, j を小さくした条件から従います。) また、 B _ {ij} = A _ {ij} - i - j とおきます。

[3]  B _ {ij} の性質

 B _ {ij} は anti-monge になる、つまり、 i _ 1 < i _ 2, j _ 1 < j _ 2 に対し、 B _ {i _ 1 j _ 1} + B _ {i _ 2 j _ 2} \geq B _ {i _ 1 j _ 2} + B _ {i _ 2 j _ 1} が成り立つことを示します。

定義から、 B _ {i _ 1 j _ 1} + B _ {i _ 2 j _ 2} - B _ {i _ 1 j _ 2} - B _ {i _ 2 j _ 1} = A _ {i _ 1 j _ 1} + A _ {i _ 2 j _ 2} - A _ {i _ 1 j _ 2} - A _ {i _ 2 j _ 1} です。これは領域  i _ 1 \leq x < i _ 2, j _ 1 \leq y < j _ 2 に含まれる点の個数となるので  0 以上です。

消す点の個数の最小値は、実は  B _ {ij} の最大値に等しくなります。これを帰納法により示します。

  •  B _ {ij} の最大値は  0 以上である。 B _ {ij} の最大値が  0 の場合、点を消さなくても操作により点をすべて取り除ける。
  •  B _ {ij} の最大値が  m であるとする。 B _ {ij} = m となるような  (i, j) のうち、 i の最小値を  i' j の最小値を  j' とおくと、 B _ {i'j'} = m であることを示す。ある  i' \leq i, j' \leq j であって  B _ {ij'} = B _ {i'j} = m であるようなものが存在する。 i = i' または  j = j' の場合は自明なのでそうでないとすると、B の anti-monge 性より  B _ {ij} + B _ {i'j'} \geq 2m で、 B の最大値は  m より  B _ {ij} = B _ {i'j'} = m となる。 B _ {i'j'} = m より、 x \leq i', y \leq j' の範囲に  i' + j' + m > 0 個の点が存在する。このうち  1 つを選んで消すと、 B _ {ij} の最大値が  m - 1 の場合に帰着する。また、それ以外の点を消すと  B _ {ij} の最大値は  m のままであるから、消す点の個数の最小値は  m 個である。

[4] データ構造による高速化

 B _ {ij} の最大値を直接求めると  \Omega(HW) 時間がかかるため、これをデータ構造を用いて高速化します。区間 add・区間 max の遅延セグメント木を用いて平面走査すると  O ( ( H+N) \log W + W) 時間で求めることができます。時間計算量は全体で  O ( ( H+N+W) ( \log H + \log W)) 時間となります。

実装例

#include <bits/stdc++.h>
using namespace std;
const int INF = 10000000;
template <typename T>
struct lazy_segment_tree{
  int N;
  vector<T> ST;
  vector<T> lazy;
  lazy_segment_tree(vector<int> &A){
    int n = A.size();
    N = 1;
    while (N < n){
      N *= 2;
    }
    ST = vector<T>(N * 2 - 1, -INF);
    lazy = vector<T>(N * 2 - 1, 0);
    for (int i = 0; i < n; i++){
      ST[N - 1 + i] = A[i];
    }
    for (int i = N - 2; i >= 0; i--){
      ST[i] = max(ST[i * 2 + 1], ST[i * 2 + 2]);
    }
  }
  void eval(int i){
    if (i < N - 1){
      lazy[i * 2 + 1] += lazy[i];
      lazy[i * 2 + 2] += lazy[i];
    }
    ST[i] += lazy[i];
    lazy[i] = 0;
  }
  void range_add(int L, int R, T x, int i, int l, int r){
    eval(i);
    if (R <= l || r <= L){
      return;
    } else if (L <= l && r <= R){
      lazy[i] += x;
      eval(i);
    } else {
      int m = (l + r) / 2;
      range_add(L, R, x, i * 2 + 1, l, m);
      range_add(L, R, x, i * 2 + 2, m, r);
      ST[i] = max(ST[i * 2 + 1], ST[i * 2 + 2]);
    }
  }
  void range_add(int L, int R, T x){
    range_add(L, R, x, 0, 0, N);
  }
  T all(){
    eval(0);
    return ST[0];
  }
};
int main(){
  int N, M;
  cin >> N >> M;
  vector<int> A(N);
  for (int i = 0; i < N; i++){
    cin >> A[i];
  }
  vector<int> B(N);
  for (int i = 0; i < N; i++){
    cin >> B[i];
  }
  vector<int> T(M), C(M);
  for (int i = 0; i < M; i++){
    cin >> T[i] >> C[i];
  }
  vector<vector<int>> C2(2);
  for (int i = 0; i < M; i++){
    C2[T[i]].push_back(C[i]);
  }
  for (int i = 0; i < 2; i++){
    sort(C2[i].begin(), C2[i].end());
  }
  int H = C2[0].size() + 1;
  int W = C2[1].size() + 1;
  vector<int> x(N), y(N);
  for (int i = 0; i < N; i++){
    x[i] = C2[0].end() - lower_bound(C2[0].begin(), C2[0].end(), A[i]);
    y[i] = C2[1].end() - lower_bound(C2[1].begin(), C2[1].end(), B[i]);
  }
  vector<vector<int>> add(H);
  for (int i = 0; i < N; i++){
    add[x[i]].push_back(y[i]);
  }
  vector<int> a(W);
  for (int i = 0; i < W; i++){
    a[i] = -i;
  }
  lazy_segment_tree<int> ST(a);
  int ans = 0;
  for (int i = 0; i < H; i++){
    for (int j : add[i]){
      ST.range_add(j, W, 1);
    }
    ans = max(ans, ST.all());
    ST.range_add(0, W, -1);
  }
  cout << ans << endl;
}