目次
AtCoder Beginner Contest 172 D,E,F問題メモ
D - Sum of Divisors
問題
- 正整数 $N$ が与えられる
- $f(x)=x$ の約数の個数、と定義する
- $\displaystyle \sum_{x=1}^{N} x \times f(x)$ を求めよ
- $1 \le N \le 10^7$
- 制限時間: 3sec
解法
公式解説に加え、maspy氏のHPに複数の解法が計算量別にまとめられている。
「$x$ は $a$ を約数に持つ」という関係を2次元表にすると以下のような感じになる
a \ x 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 o o o o o o o o o o o o o o o o 2 o o o o o o o o (2個おき) 3 o o o o o (3個おき) 4 o o o o (4個おき) 5 o o o (5個おき) 6 o o ...
個々の 'o' について、その上にある $x$ の値を全て合計したものが、答えとなる。
問題文はこれを縦に見て $x$ の軸を $1~N$ まで合計するように言っているが、視点を変えて $a$ の軸を中心に横に見ても答えは変わらない。
ある数 $a$ の約数は $a,2a,3a,...$ と $N$ を超えるまで $\dfrac{N}{a} = k$(切り捨て)個ある。
この合計は等差数列の和で $a \times \dfrac{k(k+1)}{2}$ となる。
これを $a=1~N$ まで合計すればよい。
def solve(n): ans = 0 for i in range(1, n + 1): k = n // i ans += i * k * (k + 1) // 2 return ans n = int(input()) ans = solve(n) print(ans)
$O(\sqrt{N})$ 解法
上記のように「$N$ までに $a$ の倍数は $b$ 個ある」は $b = \dfrac{N}{a}$ で求められる。
しかし、$b$ が小さい場合(1個や2個など)、$b$ が同じになる $a$ は数多く存在する。$b$ が小さい範囲については、それらをまとめて計算してやりたい。
a \ x 1 2 3 4 5 6 7 8 9 10 11 12 1 o o o o o o o o o o o o 約数12個 2 o o o o o o ~~~ 約数6個 ~~~ 3 o o o o ~~~ 約数4個 ~~~ ↑aを中心に計算 4 o o o ~~~ 約数3個 ~~~ ~~~~~~~~~~~~~ 5 o o ~~~~~~~~~~~~~~~ ↓bを中心に計算 6 o o 約数2個 7 o ~~~~~~~~~~~~~~~ 8 o 以下約数1個 ...
「$N$ までに倍数が $b$ 個ある最大の $a$」は、$a = \dfrac{N}{b}$ で求められる。
要は $a \times b \le N$ なので、$\sqrt{N}$ 付近で $a,b$ の大小が切り替わる。
$a \lt b$ の範囲は $a$ を中心に計算し、$a \gt b$ の範囲は $b$ を中心に計算すると、探索範囲の上限は $\sqrt{N}$ となる。
注意すべきは $a=b$ となるような場合で、重複して数えないようにする。
$N$ までに倍数が $b$ 個ある最大の数を $q = \dfrac{N}{b}$、$b+1$ 個(以上)ある最大の数を $p=\dfrac{N}{b+1}$ とすると、 $a=p+1,p+2,...,q$ について、$a+2a+3a+...+ba$ を求める計算になる。
これは、2つの等差数列の和のかけ算で、$\dfrac{(p+1+q)(q-p)}{2} \dfrac{b(b+1)}{2}$ で求められる。
n = int(input()) ans = 0 for i in range(1, int(n ** 0.5) + 1): k = n // i ans += i * k * (k + 1) // 2 if i != k: l = n // (i + 1) ans += i * (i + 1) * (k + l + 1) * (k - l) // 4 print(ans)
E - NEQ
問題
- 以下の条件を満たす長さ $N$ の2つの数列 $A,B$ の組が何通りあるか $\mod{10^9+7}$ で求めよ
- 条件
- 全要素は $1$ 以上 $M$ 以下の整数
- $A$ の要素は全て異なる
- $B$ の要素は全て異なる
- 全ての $i$ について、$A_i \neq B_i$
- $1 \le N \le M \le 5 \times 10^5$
解法
包除原理。
$A_i \neq B_i$ という条件がなければ、1つの数列の作り方は $M \times (M-1) \times ... $ と $N$ 個かけた数なので、${}_MP_N$、それが2つで2乗。
この中から、条件を満たさないものを消していく。
必ず $A_i=B_i$ となっている $i$ の個数を $k$ 個と固定し、残りはどっちでもよいとすると、
- $N$ 個中どの要素が同じか ${}_NC_{k}$
- 同じ要素の具体的な値の決め方 ${}_MP_k$
- 残りの具体的な値の決め方 ${}_{M-k}P_{N-k}^2$
これをあわせて、$\dfrac{N!M!(M-k)!}{k!(N-k)!(M-N)!^2}$ となる。これを $f(k)$ と表す。
包除原理を利用すると、$f(0)-f(1)+f(2)+...+f(N)$ が答えとなる。
def prepare(n, MOD): f = 1 factorials = [1] for m in range(1, n + 1): f *= m f %= MOD factorials.append(f) inv = pow(f, MOD - 2, MOD) invs = [1] * (n + 1) invs[n] = inv for m in range(n, 1, -1): inv *= m inv %= MOD invs[m - 1] = inv return factorials, invs n, m = map(int, input().split()) MOD = 10 ** 9 + 7 facts, finvs = prepare(m, MOD) c = 1 ans = 0 base = facts[n] * facts[m] * finvs[m - n] * finvs[m - n] % MOD for k in range(n + 1): tmp = base * facts[m - k] * finvs[k] * finvs[n - k] % MOD ans = (ans + c * tmp) % MOD c *= -1 print(ans)
F - Unfair Nim
問題
- 2人で ニム を行う
- 石山は $N$ 個、山 $i$ の初期の石の個数は $A_i$
- ゲームを始める前に、1度だけ山1から山2に石を移して、後手必勝にしたい
- 移せる個数は $0$ 個以上 $A_1$ 個未満(山1を0個にしてはいけない)
- 可能か判定し、可能ならその最小個数を求めよ
- $2 \le N \le 300$
- $1 \le A_i \le 10^{12}$
解法
まずニムの必勝法を知っている前提となる。全ての山のXORの値が0なら後手必勝、違うなら先手必勝。
なので、$A_3~A_N$ のXORはまず計算してやる($X$ とする)。操作後の山1、山2の個数を $p,q$ として、$p \oplus q \oplus X = 0$ ならよい。
これを含め、問題設定を考えると、以下の条件に言い換えられる。$A_1+A_2=S$ とする。
- $p \oplus q = X$
- $p+q=S$
- $0 \lt p \le A_1$
- $p$ は上記の条件を満たす中での最大値
- $A_1-p$ を求めよ
2進数で考えて、$p,q$ を0で初期化して上から貪欲に決めていく。(厳密には貪欲には決められないケースが1つあるけど、それは後述)
上から $d$ 桁目が表す数を $b$ とする。
$X$ の $d$ 桁目が'1'のとき
$p,q$ の $d$ 桁目は、どちらかが'1'、どちらかが'0'。
ここで、$S \lt p+q+b$ の場合、どちらかに'1'を立てたら $S$ をオーバーしてしまうので、不可能。
もし $p$ に $b$ を加えても $A_1$ をオーバーしないのであれば、最大化したいので $p$ の方に足す。
オーバーしてしまうなら、$q$ の方に足す。
$X$ の $d$ 桁目が'0'のとき
$p,q$ の $d$ 桁目は、どっちも'1'か、どっちも'0'か。
ここで、$S$ の値により、どちらを取るかがわかる。
- $S \lt p+q+2b$ の場合
- 両方'1'を立てたら $S$ をオーバーしてしまうので、どっちも'0'確定
- $S \ge p+q+2b$ の場合
- もし両方'0'なら、それ以下の $p,q$ の桁をすべて'1'にしても $S$ に足りなくなってしまうので、どっちも'1'確定
ただし、どっちも'1'の場合に問題が起こる。ひょっとしたら、$p$ に'1'を立てたら $A_1$ をオーバーしてしまうかも知れない。
d 123456 ------------ A1 010010 A2 100100 X 110000 ------------ p 01001_ (S 残り 2) q 10001_
6桁目、両方1を立てなければならないが、立てたら $A_1$ をオーバーする。
ここで、「それより上の桁に、$p$ が'1'、$q$ が'0'の桁があったか?」がポイントとなる。 あったら、そのうち最も低い桁の '1' を $q$ に移すことで、$p+q$ を変えずに $p$ は減り、6桁目に'1'を立ててもオーバーしなくなる。
p 000011 q 110011 ~
そういう桁が無かったら不可能。(というのが未証明)
「$X$ の $d$ 桁目が'1'のとき、最後に $p$ の方に'1'を立てた桁」を記録しておけばよい。
本番では貪欲で実装後、1ケースだけWAになり、そのケースを見つけるのができなかった。 未証明貪欲は危ないので、最初からきちんとDPで考えないとね。 (今回、貪欲解が少しだけの補正で済んだのはたまたまな気がする)
偽貪欲という結構根本的な間違いでも、WAとなるのは1ケースだけだったのが何ともデバッグの方向性を狂わせた。
import sys def solve(n, aaa): if n == 2: h, m = divmod(sum(aaa), 2) if m == 1 or h > aaa[0]: return -1 return aaa[0] - h x = 0 for a in aaa[2:]: x ^= a a0, a1 = aaa[:2] s = a0 + a1 if s & 1 != x & 1: return -1 p, q = 0, 0 b = 1 << 40 last_b = -1 while b: bx = x & b if bx == 0: if s >= 2 * b: if p | b <= a0: p |= b q |= b s -= 2 * b elif last_b > 0: p ^= last_b q ^= last_b p |= b q |= b s -= 2 * b else: return -1 else: if s < b: return -1 if p | b <= a0 and q | b > a1: p |= b last_b = b else: q |= b s -= b b >>= 1 if p == 0: return -1 assert s == 0 assert p ^ q == x assert a0 - p == q - a1 return a0 - p n, *aaa = map(int, sys.stdin.buffer.read().split()) print(solve(n, aaa))
上記をメモ化DFSにしたもの。あまり変わっていないが、こちらなら貪欲で破綻したとき、戻って違う方を試す、というのが明確になる。
import sys from functools import lru_cache def solve(n, aaa): if n == 2: h, m = divmod(sum(aaa), 2) if m == 1 or h > aaa[0]: return -1 return aaa[0] - h x = 0 for a in aaa[2:]: x ^= a a0, a1 = aaa[:2] s = a0 + a1 if s & 1 != x & 1: return -1 b = 1 << 40 MASK = (b << 1) - 1 @lru_cache(maxsize=None) def dfs(p, b): if b == 0: return p q = p ^ x & (MASK ^ ((b << 1) - 1)) if x & b == 0: if s >= p + q + 2 * b: if p | b <= a0: return dfs(p | b, b >> 1) else: return -1 return dfs(p, b >> 1) else: if s < p + q + b: return -1 if p | b <= a0: ret = dfs(p | b, b >> 1) if ret != -1: return ret return dfs(p, b >> 1) ret = dfs(0, b) if ret <= 0: return -1 return a0 - ret n, *aaa = map(int, sys.stdin.buffer.read().split()) print(solve(n, aaa))