ABC465 F - Sjeltzer?
タソサソスイ?
考え方
ID の桁数が $6$ 桁あるが、こういうときは問題を小さくして考えるのがいい。
そこで、ID が $2$ 桁の場合を考えてみよう。
例えば、ID が $12$、$14$、$23$、$32$、$44$ の $5$ つのドリンクがあり、全てサイズは $1$ とする。
ここにクエリとして 22 33 が来たとする。
もちろん愚直に $5$ 個のデータを調べれば答えは出るが、それでは計算量が $O(NQ)$ でTLE。
これをどうするか、と問われれば、これはD問題レベルである。
事前に二次元累積和を取ることで $O(N+D^2+Q)$ になる。
ただし、$D$ は使える数字の個数である $10$ である。
こう考えると $6$ 桁の場合もやることは明らかで、六次元累積和をとればよい。
方針立てはこれで全部で、無事に $O(N+D^6+Q)$ で間に合う目途が立つ。
問題はコード書き。
特に大きな問題が $3$ つある。
$1$ つめは、六次元のデータをどう持つのかという問題。
しかも、累積和を取るので各方向の長さが $11$ ほしい。
これは大きく $4$ つ(他にもあるかも)思いつくが、いずれも問題点がある。
自分にとって最も気にならない方針を選ぶこと。
| 方針 | 問題点 |
|---|---|
| 11進法で6桁の数に圧縮する | バグったときに確認しづらい |
| 10進法圧縮し、累積和の方を工夫して書く | 六次元累積和がさらに複雑化する |
| 長さ6のtupleをキーにするmapを作る | わかりやすいが、計算量が不安 |
| 六次元配列を作る | 欲しい要素を取り出すコードを書くのが大変 |
$2$ つめは、六方向の累積和をとる方法。
愚直に書くと $6$ 重ループを $6$ 回書くことになる。
これもいくつか方針がある。
| 方針 | 問題点 |
|---|---|
| 愚直に $6$ 重ループを $6$ 回書く | $1$ つでも書き間違えたら、修正は非常に困難 |
| 上手な足し方で、$6$ 重ループ $1$ 回で済ませる | どこをどう足せば正しく出るのかがややこしい |
| 順列全探索で、$7$ 重ループ $1$ 回にする | アルゴリズムが少し込み入ったものになる |
$3$ つめは、六次元累積和には、包除原理で足す値が $2^6=64$ 個もあること。
これもいくつか方針がある。
| 方針 | 問題点 |
|---|---|
| 愚直に $64$ 個の加減算を書く | $1$ つでも書き間違えたら、修正は非常に困難 |
| $2$ 要素の六重ループで書く | ネストが深いコードになる |
| bit全探索で調べる | アルゴリズムが少し込み入ったものになる |
解答例のコードでは、いずれも最後に提示した方法を採用している。
入力例1での動作
入力を受け取る。
n: 5
000000 1
314159 2
161803 10
169231 5
384400 20
q: 4
各 ID の各桁を $1$ ずつずらした位置に、サイズを加える。
累積和を取る前に $0$ でない場所は次の $5$ 箇所である。
vec[1][1][1][1][1][1]: 1
vec[4][2][5][2][6][10]: 2
vec[2][7][2][9][1][4]: 10
vec[2][7][10][3][4][2]: 5
vec[4][9][5][5][1][1]: 20
ここから $6$ 方向に累積和を取る。
例えば最初に第 $6$ 軸方向へ累積すると、000000 の $1$ は次の位置へ順に伝わる。
vec[1][1][1][1][1][1]: 1
vec[1][1][1][1][1][2]: 1
...
vec[1][1][1][1][1][10]: 1
同じ処理を残り $5$ 方向にも行うことで、六次元の累積和が完成する。
$1$ つめのクエリを見る。
x: "150001"
y: "269944"
各桁で許される数字の範囲は次の通りである。
1桁目: 1 以上 2 以下
2桁目: 5 以上 6 以下
3桁目: 0 以上 9 以下
4桁目: 0 以上 9 以下
5桁目: 0 以上 4 以下
6桁目: 1 以上 4 以下
累積和で使う半開区間の端点は次のようになる。
c0: {1, 3}
c1: {5, 7}
c2: {0, 10}
c3: {0, 10}
c4: {0, 5}
c5: {1, 5}
この $6$ 個の区間から端点を $1$ つずつ選ぶので、参照する累積和は $2^6=64$ 箇所ある。
例えば、bit 全探索の一部は次のようになる。
b |
参照位置 | 符号 |
|---|---|---|
000000 |
[1][5][0][0][0][1] |
$+$ |
000001 |
[3][5][0][0][0][1] |
$-$ |
111111 |
[3][7][10][10][5][5] |
$+$ |
残りの端点も同様に、popcount(b) の偶奇で符号を決めて足し引きする。
この範囲に入るドリンクは次の $2$ つである。
161803: 10
169231: 5
したがって、$1$ つめのクエリの答えは $10+5=15$ となる。
$4$ つめのクエリでは、例えば $1$ 桁目について 9 以上 0 以下という条件になる。
x: "999000"
y: "000444"
この場合は条件を満たす ID が存在しない。
コード上でも対応する区間の長さが $0$ になり、答えは $0$ となる。
注意点
答えは int 型からはみ出る。
long long 型を用いること。
別解
特になし。