ABC106: D - AtCoder Express 2

問題概要

 (L_1, R_1), (L_2, R_2), ..., (L_M, R_M) M個の数の組が与えられる。 1 \leq i \leq Qを満たすそれぞれの iについて p_i \leq L_jかつ R_j \leq q_iを満たす j(1 \leq j \leq M)の個数を求めよ。

(問題文は https://beta.atcoder.jp/contests/abc106/tasks/abc106_d に記載されている。)

制約

  •  1 \leq N \leq 500
  •  1 \leq M \leq 200000
  •  1 \leq Q \leq 100000
  •  1 \leq L_i \leq R_i \leq N (1 \leq i \leq M)
  •  1 \leq p_i \leq q_i \leq N (1 \leq i \leq Q)

アイディア

 Nの制約が 1 \leq N \leq 500と小さいことに注目する。

とりあえず,次のような表を作ることを考える。

 x行目の y列目には L_i = xかつ R_i = yを満たす i (1 \leq i \leq M)の個数が入っている。

例えば「入力例 3」を例に取ると,表は以下のようになる。

f:id:ransewhale:20180819152512p:plain

そして,この表の x行目の y列目の要素を t_{(x, y)}と表すことにすると,クエリ (p, q)に対する答え ans_{(p, q)} ans_{(p, q)} = \sum_{x=p}^{N} \sum_{y=1}^{q} t_{(x, y)}となる。

例えば「入力例 3」の 7つ目のクエリ (3, 8)に対する答えは,下の表の黒く塗られたセルの値の和になる。

f:id:ransewhale:20180819154336p:plain

すべての考えられる答え ans_{(p, q)} (1 \leq p \leq q \leq N)を2次元累積和によって先に求めておくこともできるが,ここではクエリごとにメモ化再帰で求めることを考える。

メモ化再帰をするために ans_{(p, q)}に関する漸化式をたてる。

「入力例 3」の 7つ目のクエリで考えると以下のようになる。

f:id:ransewhale:20180819155820p:plain

(配色が悪くてごめんなさい... 紫色の部分は赤色と青色が重なっていると考えてください。)

上の図で,黒色に塗られた部分は t_{(3, 8)}である。また,赤色(紫色を含む)部分は ans_{(3, 7)}となっていて,青色(紫色を含む)部分は ans_{(4, 8)}となっている。さらに,赤色と青色が重なって紫色になっている部分は ans_{(4, 7)}となっている。

ここから求めたい部分は赤色(紫色を含む)部分と青色(紫色を含む)部分の和からダブルカウントした紫色部分を引いたところに黒色の部分を足したものであることがわかるので, ans_{(3, 8)} = ans_{(3, 7)} + ans_{(4, 8)} - ans_{(4, 7)} + t_{(3, 8)}となることがわかる。

さらに,これを一般化すると ans_{(p, q)} = ans_{(p, q-1)} + ans_{(p+1, q)} - ans_{(p+1, q-1)} + t_{(p, q)} となる。なお,ここで p > Nまたは q = 0のとき ans_{(p, q)} = 0としておくと便利である。

実装

最初にすべての t_{(L, R)} (1 \leq L \leq R \leq N)について t_{(L, R)} = 0とし, 1 \leq i \leq Mのそれぞれについて t_{(L_{i}, R_{i})} 1を加える。

次にすべての ans_{(p, q)} (1 \leq p \leq q \leq N)について ans_{(p, q)} = -1に初期化しておく(理由は後述する)。

そして,次のような関数 solve(p, q)を考える。

  1.  p > Nまたは q = 0のとき 0を返して終了する。

  2.  ans_{(p, q)} = -1 のとき  solve_{(p, q-1)} + solve_{(p+1, q)} - solve_{(p+1, q-1)} + t_{(p, q)}を計算して ans_{(p, q)}に格納する。

  3.  ans_{(p, q)}を返して終了する。

ここで最初に ans_{(p, q)} = -1とする理由は「 ans_{(p, q)} \geq 0のときにすでに ans_{(p, q)}を計算済みである」ようにして,すべての ans_{(p, q)} (1 \leq p \leq q \leq N)について ans_{(p, q)}を1回のみ計算し,あとは保存済みの計算結果を返すようにすることで計算量を O(N^2)にするためである。

このような関数 solve(p, q)を実装した上で,あとは 1 \leq i \leq Qを満たすそれぞれの iについて ans_{(p_i, q_i)}を求めるのみである。

最終的に計算量は O(M+N^2+Qとなる)

コード

#include "bits/stdc++.h"
using namespace std;

int N,ans[555][555],t[555][555];

int solve(int p, int q){
    if(p > N || q == 0){
        return 0;
    }
    if(ans[p][q] == -1){
        ans[p][q] = solve(p,q-1) + solve(p+1,q) - solve(p+1,q-1) + t[p][q];
    }
    return ans[p][q];
}

int main(void){
    int M,Q,i,L,R,p,q;
    fill(t[0],t[555],0);
    fill(ans[0],ans[555],-1);
    cin >> N >> M >> Q;
    for(i=0; i<M; ++i){
        cin >> L >> R;
        ++t[L][R];
    }
    for(i=0; i<Q; ++i){
        cin >> p >> q;
        cout << (solve(p,q)) << endl;
    }
    return 0;
}

(提出したものはこちら)

注意点

Python3で提出した際に(おそらく再帰の深さの上限を超えたせいで)REが出ました。