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
s[0]: "000000", v[0]: 1
s[1]: "314159", v[1]: 2
s[2]: "161803", v[2]: 10
s[3]: "169231", v[3]: 5
s[4]: "384400", v[4]: 20
q: 4
各ドリンクの情報を vec に入れる。
添字を $1$ ずつずらして記録するので、初期状態で $0$ でない場所は次の $5$ 箇所である。
vec[1][1][1][1][1][1]: 1 ← 000000
vec[4][2][5][2][6][10]: 2 ← 314159
vec[2][7][2][9][1][4]: 10 ← 161803
vec[2][7][10][3][4][2]: 5 ← 169231
vec[4][9][5][5][1][1]: 20 ← 384400
その後、全方向に累積和を取る。
内部の値は非常に多くなるので、ここでは全部は書かない。
累積和を取った後は、各方向について半開区間の端点を指定し、包除原理で範囲内の総和を求められるようになる。
$1$ つめのクエリを見る。
x: "150001"
y: "269944"
コードでは、各桁について範囲の下端と上端を次のように持つ。
添字は $1$ ずつずらして記録しているため、条件 $x_k \leq s_k \leq y_k$ は累積和上では半開区間 $[x_k, y_k+1)$ に対応する。
$x_k > y_k$ であるものは存在しないので、総和を実行する。
c0: {1, 3}
c1: {5, 7}
c2: {0, 10}
c3: {0, 10}
c4: {0, 5}
c5: {1, 5}
つまり、このクエリで調べる範囲は次の通りである。
1桁目: 1 以上 2 以下
2桁目: 5 以上 6 以下
3桁目: 0 以上 9 以下
4桁目: 0 以上 9 以下
5桁目: 0 以上 4 以下
6桁目: 1 以上 4 以下
この範囲の総和を、$2^6$ 箇所の累積和の足し引きで求める。
実際の処理では、b を $0$ から $63$ まで動かし、popcount(b) の偶奇で符号を決める。
入力例1のドリンクのうち、この条件を満たすものは次の $2$ つである。
161803: 10
169231: 5
よって、答えは $10+5=15$ である。
result: 15
他のクエリも同様に、各方向の端点を作り、$2^6$ 箇所の累積和を足し引きして答えを求める。
注意点
答えは int 型からはみ出る。
long long 型を用いること。
別解
特になし。