−目次
AtCoder Beginner Contest 172 D,E,F問題メモ
D - Sum of Divisors
問題
- 正整数 N が与えられる
- f(x)=x の約数の個数、と定義する
- N∑x=1x×f(x) を求めよ
- 1≤N≤107
- 制限時間: 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 を超えるまで Na=k(切り捨て)個ある。
この合計は等差数列の和で a×k(k+1)2 となる。
これを a=1~N まで合計すればよい。
1 2 3 4 5 6 7 8 9 10 |
def solve(n): ans = 0 for i in range(1, n + 1): k = n // i ans += i * k * (k + 1) // 2 return ansn = int(input())ans = solve(n)print(ans) |
O(√N) 解法
上記のように「N までに a の倍数は b 個ある」は b=Na で求められる。
しかし、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=Nb で求められる。
要は a×b≤N なので、√N 付近で a,b の大小が切り替わる。
a<b の範囲は a を中心に計算し、a>b の範囲は b を中心に計算すると、探索範囲の上限は √N となる。
注意すべきは a=b となるような場合で、重複して数えないようにする。
N までに倍数が b 個ある最大の数を q=Nb、b+1 個(以上)ある最大の数を p=Nb+1 とすると、 a=p+1,p+2,...,q について、a+2a+3a+...+ba を求める計算になる。
これは、2つの等差数列の和のかけ算で、(p+1+q)(q−p)2b(b+1)2 で求められる。
1 2 3 4 5 6 7 8 9 |
n = int(input())ans = 0for 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) // 4print(ans) |
E - NEQ
問題
- 以下の条件を満たす長さ N の2つの数列 A,B の組が何通りあるか mod109+7 で求めよ
- 条件
- 全要素は 1 以上 M 以下の整数
- A の要素は全て異なる
- B の要素は全て異なる
- 全ての i について、Ai≠Bi
- 1≤N≤M≤5×105
解法
包除原理。
Ai≠Bi という条件がなければ、1つの数列の作り方は M×(M−1)×... と N 個かけた数なので、MPN、それが2つで2乗。
この中から、条件を満たさないものを消していく。
必ず Ai=Bi となっている i の個数を k 個と固定し、残りはどっちでもよいとすると、
- N 個中どの要素が同じか NCk
- 同じ要素の具体的な値の決め方 MPk
- 残りの具体的な値の決め方 M−kP2N−k
これをあわせて、N!M!(M−k)!k!(N−k)!(M−N)!2 となる。これを f(k) と表す。
包除原理を利用すると、f(0)−f(1)+f(2)+...+f(N) が答えとなる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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, invsn, m = map(int, input().split())MOD = 10 ** 9 + 7facts, finvs = prepare(m, MOD)c = 1ans = 0base = facts[n] * facts[m] * finvs[m - n] * finvs[m - n] % MODfor k in range(n + 1): tmp = base * facts[m - k] * finvs[k] * finvs[n - k] % MOD ans = (ans + c * tmp) % MOD c *= -1print(ans) |
F - Unfair Nim
問題
- 2人で ニム を行う
- 石山は N 個、山 i の初期の石の個数は Ai
- ゲームを始める前に、1度だけ山1から山2に石を移して、後手必勝にしたい
- 移せる個数は 0 個以上 A1 個未満(山1を0個にしてはいけない)
- 可能か判定し、可能ならその最小個数を求めよ
- 2≤N≤300
- 1≤Ai≤1012
解法
まずニムの必勝法を知っている前提となる。全ての山のXORの値が0なら後手必勝、違うなら先手必勝。
なので、A3~AN のXORはまず計算してやる(X とする)。操作後の山1、山2の個数を p,q として、p⊕q⊕X=0 ならよい。
これを含め、問題設定を考えると、以下の条件に言い換えられる。A1+A2=S とする。
- p⊕q=X
- p+q=S
- 0<p≤A1
- p は上記の条件を満たす中での最大値
- A1−p を求めよ
2進数で考えて、p,q を0で初期化して上から貪欲に決めていく。(厳密には貪欲には決められないケースが1つあるけど、それは後述)
上から d 桁目が表す数を b とする。
X の d 桁目が'1'のとき
p,q の d 桁目は、どちらかが'1'、どちらかが'0'。
ここで、S<p+q+b の場合、どちらかに'1'を立てたら S をオーバーしてしまうので、不可能。
もし p に b を加えても A1 をオーバーしないのであれば、最大化したいので p の方に足す。
オーバーしてしまうなら、q の方に足す。
X の d 桁目が'0'のとき
p,q の d 桁目は、どっちも'1'か、どっちも'0'か。
ここで、S の値により、どちらを取るかがわかる。
- S<p+q+2b の場合
- 両方'1'を立てたら S をオーバーしてしまうので、どっちも'0'確定
- S≥p+q+2b の場合
- もし両方'0'なら、それ以下の p,q の桁をすべて'1'にしても S に足りなくなってしまうので、どっちも'1'確定
ただし、どっちも'1'の場合に問題が起こる。ひょっとしたら、p に'1'を立てたら A1 をオーバーしてしまうかも知れない。
d 123456 ------------ A1 010010 A2 100100 X 110000 ------------ p 01001_ (S 残り 2) q 10001_
6桁目、両方1を立てなければならないが、立てたら A1 をオーバーする。
ここで、「それより上の桁に、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ケースだけだったのが何ともデバッグの方向性を狂わせた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
import sysdef 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 - pn, *aaa = map(int, sys.stdin.buffer.read().split())print(solve(n, aaa)) |
上記をメモ化DFSにしたもの。あまり変わっていないが、こちらなら貪欲で破綻したとき、戻って違う方を試す、というのが明確になる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import sysfrom functools import lru_cachedef 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 - retn, *aaa = map(int, sys.stdin.buffer.read().split())print(solve(n, aaa)) |

