−目次
トヨタ自動車プログラミングコンテスト2024#9(AtCoder Beginner Contest 370)F,G問題メモ
F - Cake Division
問題文
- 円形のケーキがあり、ケーキは中心から外周への切り目によって N 個の扇形のピースに分けられています。
- ピースおよび切り目には時計回りに 1,2,…,N の番号が付けられており、ピース i の質量は Ai です。
- このケーキを以下の条件を満たすように K 人に分けます。
- すべての人が 1 つ以上の連続するピースを受け取る
- 誰も受け取らないピースは存在しない
- 上の 2 つの条件を満たすという条件下で min(w1,w2,…,wK) が最大になるようにする
- ただし、i 番目の人が受け取るピースの質量の合計を wi とする
- 以下の2つを求めてください。
- 条件を満たす分け方における min(w1,w2,…,wK) の値
- 条件を満たすどのような分け方でも切られることのない切り目の個数
制約
- 2≤K≤N≤2×105
- 1≤Ai≤104
- 入力される値はすべて整数
解法
ABCでもよく見る典型がいくつか組み合わさった問題。
自然な形で上手く組み合わさっているので、気付くとなかなか気持ちいい。
1人1人が受け取るピースの塊を、「分け前」と呼ぶことにする。
円形のケーキじゃなくて両端があるなら、大枠は典型90問の第1問目でも名高いこの問題に近くなる。
両端が分かっている場合、「全体を、K 個以上の、質量 x 以上の分け前に分割できるか?」が解ければ、x を二分探索することで解ける。
これは端から「ピースの質量合計が x 以上になったらそこですぐ切る」ことを繰り返せば、O(N) で判定できる。
ただ、今回は端が決まっていないので、切り目 1 から切った場合は無理でも、他の切り目から始めたら行けるかもしれない。
i 1 2 3 4 5 K=3 A 6 8 6 4 3 x=8 は可能? 1から始めると、i=1,2 / 3,4 /(残りは8に満たない)で2個。NG 2から始めると、i=2 / 3,4 / 5,1 3個に分けられる。OK
N 箇所全ての切り目から1回 O(N) の判定を試すのは O(N2) で無理。
ただ、どこから始めても、その過程で「i が分け前の開始ピースとなったら、どこまで続いて、次に切られるのはどこ」という関係性は変わらない。
例えば、上例では i=3 から始まった分け前は、共通して 3,4 まで続き、次の分け前は i=5 から開始される。
分け前が i から始まった場合の次の分け前の開始点を x=8,i=3 に対して B8[3]=5 というように表現すると、B8=(3,3,5,7,7) となる。(1周を超える分でも番号は継続するとして)
すると x を固定した場合の、各 i に対する判定問題は 「i を Bx[i] に置き換える操作を K 回行って、 最終的な i が1周以内(=i+N 以下)に収まっていればよい」ということになる。
これはダブリングで高速化でき、全ての i=1~N に対して O(NlogK) で求められる。
実装上のテクニックとして、OutOfIndex にならないよう Bx は2周分+さらに末尾に 2N+1 を追加しておき、
遷移後の値が 2N+1 を超える場合は 2N+1 で上限を切るとよい。
ダブリング中、2N+1 を超えそうになったら 2N+1 でループするようになる。
1周以内に収まっているかの判定の最大値が i=N から始まったときの「2N 以下か?」であり、
2N+1 まで行ったらどの i に対してもNGであることが確定するので、それ以上の具体的な値の情報は必要ない。
判定問題を満たす i が1つでもある最大の x が、答えの1つめ。
改めてダブリングによる K 回遷移後の i を求め、 1周以内に収まっていない i の個数が答えの2つめ「そこで切ったら最大値を達成できない切り目」の個数となる。
G - Divisible by 3
問題文
- 正整数 n の正の約数の総和が 3 で割り切れる時、n を「良い整数」と呼びます。
- 正整数 N,M が与えられます。
- 長さ M の正整数列 A のうち、 A の要素の総積が N 以下の良い整数になるものの個数を 998244353 で割った余りを求めてください。
制約
- 1≤N≤1010
- 1≤M≤105
- N,M は整数
解法
「乗法的関数のPrefix-Sum」という手法を用いる。知らないと無理。知ってても本番中に考察して応用して実装するのは厳しい。
素朴なTLE解法
x=pe11pe22... と素因数分解できるとき、 約数の総和は (1+p1+p21+...+pe11)(1+p2+p22+...+pe22)... で求められる。
この (1+p+p2+...+pe) というパートは、
- p=3 のとき、絶対に3の倍数にはならない
- p≡1mod3 のとき、e≡2mod3 なら3の倍数。他は3の倍数でない。
- p≡2mod3 のとき、e≡1mod2 なら3の倍数。他は3の倍数でない。
となり、少なくともどれか1つの素因数とその指数 (pi,ei) で (1+pi+p2i+...+peii) が3の倍数となってくれれば、x は「良い整数」となる。
この判定法を用いて、x=1~N に対し x が良い整数である時のみ以下の問題を解くと、その総和が答えとなる。
- 総積が x となるような、長さ M の数列の個数を求めよ
これは x の素因数毎に独立に考えることができて、例えば x=360=233251 を例に取ると、
- 2を3つ、重複を許して M 個の要素のどれかに振り分ける
- 3を2つ、重複を許して M 個の要素のどれかに振り分ける
- 5を1つ、重複を許して M 個の要素のどれかに振り分ける
これらの積で、3HM×2HM×1HM で求められる。
だが、これは当然、x を最大 1010 個 調べないといけないので、TLE。
乗法的関数
正整数を引数に取る関数 f(x) が次の条件を満たすとき、f(x) は乗法的関数(Multiplicative Function)であるという。
- 互いに素な a,b に対して、f(ab)=f(a)f(b)
上記の通り、「g(x):= 総積が x となるような、長さ M の数列の個数」は、乗法的関数である。
このような乗法的関数の1から N までの総和 F(N)=N∑x=1g(x) は、 後述するが高速に計算する手法がある。
今回は「良い整数」のみについて g(x) を足し合わせないといけないので そのままは適用できないが、工夫すれば適用できるようになる。
まずは、良い整数の要素は除いて、F(N) を求めることを考える。
Black Algorithm
特殊な N 頂点の木でDPをするアルゴリズム。
N=25 ① ,-----------------,-'`----,--,--,--,--,--, ② ③ ⑤ ⑦ ⑪ ⑬ ⑲ ㉓ ,--'`--,--,--,--, /`,--, | ④ ⑥ ⑩ ⑭ ㉒ ⑨ ⑮ ㉑ ㉕ /`,--, | ⑧ ⑫ ⑳ ⑱ /\ ⑯㉔
- 木の生成規則
- 頂点 1 を根とする根付き木である
- 頂点 v(v≥2)は、「v を最大の素因数で割った値」の頂点を親に持つ
- ㉑ =3×7 は、最大の素因数 7 で割った③を親に持つ
- ⑱ =2×32 は、最大の素因数 3 で割った⑥を親に持つ
以下、Lpf(x):=x の最大の素因数 とする。
親から子への視点で整理すると、 頂点 v は「Lpf(v)~⌊Nv⌋」の間の素数を v にそれぞれかけた番号の子を持つ。
- ②は、Lpf(2)=2~⌊N2⌋=12 間の素数 (2,3,5,7,11) を自身にかけた④⑥⑩⑭㉒の子を持つ。
- ③は、Lpf(3)=3~⌊N3⌋=8 間の素数 (3,5,7) を自身にかけた⑨⑮㉑の子を持つ。
仮に(N が大きいので実際には無理だが)N 以下の全ての素数 p=2,3,5,... につき g(p) が計算済みであり、 累積和を取ることで、任意の区間の「素数のみの g(p) の和」が O(1) で求められるとする。
- Fprime(N):=1 から N までのうち、素数のみの g(p) の和
- G(l,r):=l より大きく r 以下の数のうち、素数のみの g(p) の和(=Fprime(r)−Fprime(l))
すると、乗法的関数の性質「互いに素なら、g(ab)=g(a)g(b)」を利用すると、 たとえば②の子④⑥⑩⑭㉒(それぞれ2に2,3,5,7,11がかけられた数)のうち、
- ④だけは、②にも含まれる素因数がかけられ g(4)=g(2)g(2) とは限らないので、個別の計算をする。
- ⑥以降は、木の生成規則から、確実に②とは互いに素な数がかけられている。
- よって、g(6)+g(10)+g(14)+g(22) は、g(2)×G(2,12) でまとめて求められる。
これは、v= ③や⑧など、他の内部ノード(葉以外の頂点)でも G を使って同様のことが言え、
- v と同じ素因数がかけられる唯一の子は、個別計算
- それより大きい素数がかけられた数は、内部ノードを v として、g(v)×G(Lpf(v),⌊Nv⌋) でまとめて計算
という処理を全ての内部ノードについて行うことで、x=1~N の全ての g(x) の総和が求められることになる。
葉ノードは個別処理する必要が無い、というのがこのアルゴリズムの肝となる。
で、実際にこの木において内部ノードの数は少なく、N=1010 に対して 6.3×106 程度で済むらしい。
これで、「G:ある区間の素数だけの g(x) の和」が求められれば、現実的な時間で F(N) を求められることがわかった。
Lucy DP
肝心の G をどうやって求めるか。
N≤1010 のような制約で、N 以下の全ての素数に対して g(p) なんて求められない。
よく考えると、Black Algorithm の過程で実際に必要とされる G(l,r) の l,r の値って、実はそんなに多くない。
内部ノード x の処理を考えるとき、必要となる l,r は
- l は、x の最大の素因数。
- x は内部ノード(子を持っている)前提なので、xLpf(x)≤N。
- x も当然 Lpf(x) を素因数に持っているので、l=Lpf(x)≤√N
- r は、⌊Nx⌋
- つまり「Q(N):=N を何らかの整数で切り捨て除算した値として取り得る数」しか r にならない。
- たとえば Q(57)=(1,2,3,4,5,6,7,8,9,11,14,19,28,57)
- これは、前半 k=1~√N に対して後半が Nk としてそれぞれ対応する関係にあり、約 2√N 個しかない。
l の値の範囲は r に内包されるので、結局、Q(N) に含まれる各数 qi について Fprime(qi) が求められれば、 Black Algorithm の過程で必要な G(l,r) に対して全て答えられるということになる。
で、乗法的関数においてこれをするのがLucy DPとなる。
良い整数だけ
今回は、良い整数だけを数えなければならない。
Black Algorithm で x の子の g(∗) をまとめて足し上げる時に、
- Lpf(x) 未満の素因数により、x の約数が3の倍数となることが確定している場合
- つまり、Lpf(x) 未満の素因数 p とその指数 e で、(1+p+p2+...+pe) が3の倍数となるものが存在する
- この場合、x の子も全て良い整数であることが確定しているので、全て足し上げて良い。
- Lpf(x) によってのみ、x の約数が3の倍数となる場合
- 一番左の子(x×Lpf(x))は、個別に確認の必要がある。
- 一番左の子以外は、Lpf(x) によって良い整数であることが確定するので、全て足し上げて良い。
- x が良い整数でない場合
- 一番左の子は、個別に確認する。
- 一番左の子以外は、「3で割って2余る素数 p をかけた子に対してのみ、g(xp) を足し上げる」ことになる。
- x と互いに素な p に対して xp が良い整数になるには、(1+p) が3の倍数となる必要があるため。
よって、G に加えて「H(l,r):=l より大きく r 以下の、3で割って2余る素数のみの g(p) の総和」があれば、 良い整数に限定した数え上げも可能となる。
H は、Lucy DPで「素数全体」に加え 「3で割って1余る素数の個数」「3で割って2余る素数の個数」に分けて管理していくと、上手く計算できる。
Black Algorithm 上でDFSをするにあたり、
- g(x)
- Lpf(x) とその指数 e
- 既に Lpf(x) 未満の素因数により x が良い整数であることが確定しているフラグ
などを持ちながらおこなうとよい。