aacord’s memo

abcを中心にpythonで解いた問題のメモ、整理をしています。緑になった。

abc 172 F-Unfair Nim (python)

4完 パフォ1412 でした。最近パフォが1400 くらいで安定している。今はいいけど水色になってからは困るなあ。
コンテスト後に F を通しました。考察はあと一歩だったけど、実装の場合分けは無理でした。

まず、Nim の必勝法は A1^A2^...^An = 0 なら 後手が必ず勝ちます。コンテスト中に蟻本を読んでズルした。理由ははしょります。
なので、s = A1+A2, c = A3^A4^...^An とおくとこの問題は、
(A1-i)^(A2+i) = c を満たす最小の i を求める、という問題になります。(A1^A2^c = 0 なので)
そこから、s の式に注目すると、P + Q = (P ^ Q) + 2*(P & Q) という xor の問題で使うらしいテクニックがあるので、s = A1+A2 の式を s = (A1-i)^(A2+i) + ( (A1-i) & (A2+i) )*2 と書き換えると、

x = (A1-i), y = (A2+i) とおくとこの問題は連立方程式
x ^ y = c ー①
x & y = (s-c)/2 -②
をみたす1≦ x ≦ A1 のなかで最大のものを求めればいいことになります。
とりあえず、s < c のものや、(s-c)/2 が少数のものはその時点で答えは -1 としていいです。
さらに sc = (s-c)/2 とおいて ① に ^x を行って y = c^x とおくと①、②は
y = c^x -③
x & (c^x) = sc -④
と同値になります。
④の式の嬉しいところは xor も and も各桁ごとに独立なので、(繰り上がり、繰り下がりがない)
桁ごとに計算をしていい点です。

ここで、2進数で c = 1100, sc = 1010 としたとき、x の各桁はどのようになるのかを見ていくと、
(cc, ss, xx) = (c の桁の値、sc の桁の値、そのときのxの桁の値)とすると
1.(cc, ss) = (0, 0) → xx = 0
2.(cc, ss) = (0, 1) → xx = 1
3.(cc, ss) = (1, 0) → xx = 0, 1
4.(cc, ss) = (1, 1) → これをみたす xx は存在しない
となります。
なので、 x は1. ~ 4 . と1≦ x ≦ A1 満たすもののなかで最大のものを求めればいいことになります。(これで④は成立して、③は x が存在すれば y も存在するので成立する)

1. ~ 4. を処理していく方針ですが、まず 4. は事前に

if c & sc != 0 :
  print(-1)
  exit()

と書いておけば気にしなくていいです。
あとはまず c と sc の桁を上から見ていって、1. と 2. なら x の桁は確定できるのでさせておき、3.なら x の桁を '?' としておきます。(例えば、c = 10010, sc = 100 なら x = '0?010?' となる)
この時点で x が A1 よりも超えることが確定していたら答えは -1 です。
あとは '?' の部分は A1 を超えない中で最大になるように前から決めたら終わりです。
出力する答えは A1 - x になります。

import sys
read = sys.stdin.buffer.read
readline = sys.stdin.buffer.readline
readlines = sys.stdin.buffer.readlines

n = int(readline())
a = list(map(int,readline().split()))
s = a[0]+a[1]
# n = 2 は 別で処理
if n == 2:
  if a[0] < a[1] or s%2 == 1:
    print(-1)
  else:
    print(a[0]-s//2)
  exit()
  
# c を計算
c = 0
for i in a[2:]:
  c ^= i

# 取り合えず答えが -1 になるものを消す
sc = (s-c)//2
if s < c or (s-c)%2 == 1 or c&sc != 0:
  print(-1)
  exit()

# 1. ~ 3. の動作
# x は文字列、chk が実際の x の値
bb = a[0].bit_length()
x = ''
chk = 0
for i in range(bb-1,-1,-1):
  if (c>>i&1)|(sc>>i&1) == 0:
    x += '0'
  elif c>>i&1 == 0 and sc>>i&1 == 1:
    x += '1'
    chk += 2**i
  else:
    x += '?'

# この時点で x の値が A1 よりも超えていたら答えは -1 
if chk > a[0]:
  print(-1)
  exit()
  
# x が A1 を超えない最大値になるよう決めていく
for i in range(bb):
  if x[i] == '?':
    if a[0] >= chk+2**(bb-1-i):
      chk += 2**(bb-1-i)
     
# 条件を x の最小値は1なのでchk が1未満 (0) なら答えは -1
if chk == 0:
  print(-1)
else:
  print(a[0]-chk)