Loading [MathJax]/jax/output/CommonHTML/jax.js

AtCoder Beginner Contest 230 F,G問題メモ

AtCoder Beginner Contest 230

平日コン、のーみそが疲れてると問題文の内容が頭に入ってこない。

F - Predilection

問題

  • 長さ N の数列 A1AN
  • 隣り合う2要素を、その和の1要素に置き換える、という操作を何度でも行える
  • 最終的な数列としてあり得るものは何通りか、mod998244353 で求めよ
  • 1N2×105
  • |Ai|109

解法

累積和で考える。

A の累積和を B 、最終的な数列の一例を C 、その累積和を D とする。

すると D は、B からいくつかを(順番を変えずに)かいつまんだ部分列と見ることができる。
(ただし最後の BN は必ず使う)

A   1  0  2  1 -3  2  1  0
B   1  1  3  4  1  3  4  4

    |-->  >  |----------->
C      1  2              1
D      1  3              4

CD は1対1対応するので、D の個数を数えればよい。

列として同じになってしまうものが存在するので、それを重複して数えてはいけない。

i   1  2  3  4  5  6  7  8
--------------------------
A   1  0  2  1 -3  2  1  0
B   1  1  3  4  1  3  4  4
    >  |----------->  |-->
C   1              2     1    上の例とC,Dが同じ
D   1              3     4

DPで考える。

  • DP[i]=i 番目の要素まで考えて、i 番目の要素は必ず使うときの、B から取れる部分列の個数

遷移は、基本的には DP[1]DP[i1] のそれぞれから遷移できて以下のようになる。

  • i1j=1DP[j]

ただし、複数含まれる要素は最後の1要素からしか遷移できない

i   1  2  3  4  5  6  7  8
--------------------------
B   1  1  3  4  1  3  4  4
                      *       DP[7] へ遷移できるのは
             *  *  *          この3つだけ

i=3 と i=6 は Bi が 3 で同じ。
DP[3] に数えられている具体的な部分列は (1 3) (1 1 3) などがあるが、
これらは必ず全て、DP[6]にも数えられている。

これは、i=1,2,...DP を求めていく過程で、「要素 a が最後に出現した i」を辞書型などで更新していくとよい。

O(N) で最後まで求められる。

Python3

G - GCD Permutation

問題

  • 1,..,N の順列 P1,...,PN が与えられる
  • 以下を全て満たす (i,j) の組の個数を mod998244353 で求めよ
    • 1ijN
    • GCD(i,j)1
    • GCD(Pi,Pj)1
  • 1N2×105
  • 時間制限: 5sec.

解法

基本的に公式Editorialの踏襲。

GCD条件が1つの場合

仮に、GCD(Pi,Pj)1 を無視して GCD(i,j)1 だけに絞って考えると、

  • 互いに2の倍数だったら当てはまる
  • 互いに3の倍数だったら当てはまる
  • ……

と、互いに何らかの数の倍数だったら当てはまる。

たとえば2の倍数は N2 個(切り捨て)あるので、 これを s とおくと s(s+1)2 個のペアが存在する。
(この問題では自分自身とのペアも許される点に注意)

a=2N について、a の倍数の中で作れるペアの個数 Pair(a)=Na(Na+1)2 を合計すると答え……にはならない。

重複が発生するので、上手く除かないといけない。
たとえば6と12は、2,3,6 の3回で数えられてしまう。

メビウス関数

そういうときに便利に使えるのがメビウス関数(を改変したもの)となる。
包除原理の約数特化版みたいな感じ。

  • 本来の メビウス関数 は「互いに素なもの」の個数を数える時などに使うため、下記と少し異なる

˜μ(n)={1(n(n=2,3,30))1(n(0)(n=6,10,210))0((n=1,4,8,9,12))

2以上のどんな整数についても、約数の ˜μ(x) を足し合わせると、必ず 1 になるという性質を持つ。

なので、6と12の場合も、

  • ˜μ(2),˜μ(3) は1なので、その倍数から作られるペアは 1 回ずつ数えられる
  • ˜μ(6)=1 なので、その倍数から作られるペアは 1 回数えられる(負に寄与する)

とすると、きちんと重複をのぞける。

よって、1N で互いに素でない組の個数は、Na=2˜μ(a)Pair(a) となる。

二重化

さて、これでGCD条件が1つの場合は答えを求められた。
しかし今回はGCD条件が2つある。

a=2N について、添字が互いに a の倍数である Pa,P2a,P3a,... の中に限定して、 再び b=2N について「2の倍数の個数」……「N の倍数の個数」を調べていけば答えは求まる。

g(a,b) を「添字が a の倍数である Pi に限定した中での b の倍数の個数」とすると、 ab˜μ(a)˜μ(b)g(a,b)(g(a,b)+1)2 が答えとなる。

添字ならば規則的に並んでいるため「a の倍数の個数」は割り算で求められたが、 抽出された中では「b の倍数の個数」は実際に全部試すしかない。 そのためこれを愚直に行うと、計算量は全体で O(N2) となってしまう。

しかし、個数が限定されている場合、メビウス関数の性質も併せて考えると、実は調べるべき b はそれほど多くない。

b の方を1つ1つ試すのではなく、各 Pa,P2a,... について、それらが持つ約数の方からアプローチする。

つまり、「count[b]=b を約数に持つ、限定された中での Pi の個数」とした記録用辞書を作っておき、
たとえば Pi2,3,6 を約数に持つのであれば、count[2],count[3],count[6] にそれぞれ+1する。

Pa,P2a,... についてこれを行えば、count から各値を約数に持つ Pi が何個あるかがわかる。

だがそれでも、約数は N2×105 の中では最大 160 個とかになり、言語によってはやや厳しい。

さらに絞ると、同じ素因数を2個以上含む約数は ˜μ(x)0 になる(答えへの寄与も0)ので、 約数のうちそれらは無視できる。
Pi2p3q5r という風に素因数分解できるなら、 count に記録すべき約数は、2,3,5 をそれぞれ含むか含まないか、から 1 を除いた 231=7 通りだけでよい。

これなら、Pi2×105 の範囲で素因数の種類数はたかだか 6 個(考慮すべき約数は 261=63 個)、 しかもそんな特異な数は限られていて多くはせいぜい 13 個くらいなので、間に合う。

まとめ

あらかじめ、1N について「˜μ(x)」および「約数のうち ˜μ(x) が0でないもののリスト」を計算しておく。

a=2N について、以下の要領で Pa,P2a,... を調べていく。

  • 空の記録用辞書 count を作る
  • Pa,P2a,... について、約数リストに存在する数について、count[b]+=1 としていく
  • 最終的に count に記録された各 (b,cnt) につき、˜μ(a)˜μ(b)cnt(cnt+1)2 を答えに加算する

1N の素因数の種類数の最大値を d(N) として、O(2d(N)NlogN)(実際はもっと少ない)で求められる。

Python3

programming_algorithm/contest_history/atcoder/2021/1203_abc230.txt · 最終更新: 2021/12/27 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0