−目次
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 ans n = 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 = 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 の組が何通りあるか 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, 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 の初期の石の個数は 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 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にしたもの。あまり変わっていないが、こちらなら貪欲で破綻したとき、戻って違う方を試す、というのが明確になる。
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 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)) |