東京海上日動 プログラミングコンテスト2021(AtCoder Regular Contest 122) A,C,D,E問題メモ
問題
長さ $N$ の非負整数列
$N-1$ 箇所の各項間に演算子 '+' または '-' を入れて1つの式を作る
演算子の入れ方は $2^{N-1}$ 通りあるが、そのうち '-' が2回以上連続で登場しない式を「良い式」とする
全ての良い式の答えの総和を $\mod{10^9+7}$ で求めよ
$1 \le N \le 10^5$
解法
主客転倒。
良い式のうち、$A_i$ が正で寄与する式の個数を $p_i$、負で寄与する式の個数を $q_i$ とすると、
$A_i \times (p_i-q_i)$ を各 $i$ について総和をとったものが答え。
$p_i,q_i$ は、自身の左右に、記号を自由に決められる箇所がそれぞれ何個連続しているかで決まる。
'-' を置くとその両隣は '+' を置くしかないので、自由に決められる箇所は左右それぞれ1ずつ短くなる。
3 1 4 1 5 9 2
^
+を置いたとき 左2 右3 ... p3 = f(2)*f(3)
-を置いたとき 左1 右2 ... q3 = f(1)*f(2)
※ f(x)は長さx+1の良い式の個数
$f(x)$ は以下のDPで求められる。
そしてこれは、遷移式を作るとわかるがフィボナッチ数列となる。
以下の、末端部分に関する処理に留意する。
Python3
n = int(input())
aaa = list(map(int, input().split()))
MOD = 10 ** 9 + 7
fib = [1] * (n + 1)
for i in range(2, n + 1):
fib[i] = (fib[i - 2] + fib[i - 1]) % MOD
ans = aaa[0] * fib[n] % MOD
for i in range(1, n):
ans += aaa[i] * fib[i] * fib[n - i]
ans -= aaa[i] * fib[i - 1] * fib[n - i - 1]
ans %= MOD
print(ans)
C - Calculator
問題
解法
$(x,y)$ の組を頂点として、$(0,0)$ から4つの操作の通りに遷移するBFSなどは、
頂点数がアホほど多くなるので無理。
ならばと $(N,y)$ という最終状態から逆にたどることもできるが、
その場合 $y$ には何の値が入っていてもよく、何が最適なのか決められない。
どうも最短経路的に解くのは難しそう。
$N$ の上限は $10^{18}$ とやたら大きいが、どんなに大きくても130回以内でできるらしい。
そのためまずはとにかく大きくする操作方法を考える。
$x$ や $y$ が1以上なら、1を足すより操作3,4でもう一方を足した方がよいので、
はじめに操作1と2で $(1,1)$ にした後は3と4を交互に行うのがよさそう。
その場合、$x,y$ はフィボナッチ数列の項を交互にたどる。
x y x y x y x y x y x y ...
1 1 2 3 5 8 13 21 34 55 89 144 ...
と、だいたい88項目くらいで $10^{18}$ を超えるので、
数を大きくするのはこの方法で大丈夫そう。
あとはフィボナッチ上に現れない $N$ の場合にどう調整するかだが、
$N$ をいくつかのフィボナッチの項の和にすることはできそう。
140 = 89 + 34 + 13 + 3 + 1
操作3,4を繰り返す中の適当なタイミングで $x$ や $y$ に1足してやれば、
そこからもまたフィボナッチ数列が始まる。
(見た目上は一緒くたになった数字だが、個々のフィボナッチに分解できる)
x y x y x y x y x y x y ...
1 1 2 3 5 8 13 21 34 55 89 144 ...
1 1 2 3 5 8 13 21 34
1 1 2 3 5 8 13
1 1 2 3
1 1
====
140
(見た目上のx,y)
1 1 2 4 7 12 20 32 53 86 140
↓ ↓ ↓ ↓
3 8 33 87
$N$ がフィボナッチ数列の $c_1,c_2,...$ 項目の和で表現できる場合、
$c_{\max}$ 以外の各 $c_i$ について、$c_{\max}$ 項目でちょうど希望の値にするためにはどこで1を足しておく必要があるか
操作3,4を交互に行う中で、$d_i$ 回目の操作タイミングで、操作を行った方に +1 する
と、できあがり。
Python3
n = int(input())
fib = [1] * 131
for i in range(2, 131):
fib[i] = fib[i - 2] + fib[i - 1]
x = n
use = []
for i in range(130, -1, -1):
if x >= fib[i]:
use.append(i)
x -= fib[i]
fc = use[0]
rev = fc % 2
use = {fc - u - 2 for u in use[1:]}
ans = [1, 2]
x = 1
y = 1
for i in range(fc - 1):
if i % 2 == 0:
ans.append(3 + rev)
x += y
if i in use:
ans.append(1 + rev)
x += 1
else:
ans.append(4 - rev)
y += x
if i in use:
ans.append(2 - rev)
y += 1
print(len(ans))
for a in ans:
print(a)
# print(f'x:{x} y:{y}')
解法2
フィボナッチのように、直前の2項を足す数列の隣り合う項の比は、黄金比 $=1.618...$ に近づいていく。
操作3,4を繰り返すのが手っ取り早く値が増加して、その比が黄金比になるとしたら、最終状態の $(N,y)$ は $N:y=1.61803398874989484820...:1$ に近くなっているはず。
操作を逆からたどることを考えると、交互に引き続けることで、ちょうどいい具合にこの比が保たれる(黄金比なので)。
勿論、$x,y$ は整数であるため誤差が蓄積されるので調整は必要となる。
大きい方から小さい方を引いてもまだ大きい方が大きい、ということがあり得るが、
連続になってしまってもよいので「大きい方から小さい方を引く」のルールで操作する。
また、一方がゼロになったら、もう一方は操作1,2で1ずつ足していって作る。
これらの状態になるのは値が相応に小さくなってからのはずなので、必ず間に合うという保証が難しいが、いけるらしい。
D - XOR Game
問題
$2N$ 個の整数 $A_1,A_2,...,A_{2N}$
AliceとBobがゲームをする
手順とルール
両者が最適に行動したときのスコアを求めよ
$1 \le N \le 2 \times 10^5$
$0 \le A_i \lt 2^{30}$
解法
まず、こういうのは高位のbitから決めていくのがよさそう。
後攻のBobに圧倒的に利がある。
$A_i$ の中での最高位bitを '10000' とする。
ここが立った $A_i$ の個数を数える。
偶数個
ことで、絶対にスコアの '10000' は立たせないことができる。
なので、以下の2つの問題に分割でき、その大きい方となる。
奇数個
必ずどこかでは '10000' が立ってる1つと立ってない1つをペアにすることになる。
従って、スコアにおいて '10000' のbitは立ってしまう。
Bobは「立ってる1つと立ってない1つのペアで、XORが最小のもの」を見つけることになる。
このペア以外の整数をAliceが選んできたら、
Bobも '10000' の立ってる/立ってないが同じ、ペア以外の整数の中から適当に1つ選べばよい。
このペアの整数をAliceが選んできたら、
すかさずBobはペアのもう一方を選べばよい。
これでBobは最小値を達成できる。
見つけ方だが、なるべく '1000' 以下の高位bitから見たときに接頭辞が長く共通している2数を選べばよい。
そのようなものがない
(たとえば '10000' が立ってる整数の中では '100' が立ったものしか無く、
'10000' が立ってない整数の中では '100' が立ってないものしか無い)場合は、
仕方ないので '100' は立てて、さらに下位のbitを共通させられるか調べていく。
たかだか深さが30程度なので、再帰関数で実装した。
EditorialではTrie木を構築するとあった。なるほど。
Trie木なら、多対多からなるべく共通させられるようなものを探すのに適している。
Python3
def solve2(i, on, off):
if len(on) == 0 or len(off) == 0:
return 1 << 60
if len(on) * len(off) < 1000:
res = 1 << 60
for a in on:
for b in off:
res = min(res, a ^ b)
return res
msb = 1 << i
on0 = set()
on1 = set()
off0 = set()
off1 = set()
for a in on:
if a & msb:
on1.add(a ^ msb)
else:
on0.add(a)
for a in off:
if a & msb:
off1.add(a ^ msb)
else:
off0.add(a)
if i == 0:
if len(on0) > 0 and len(off0) > 0:
return 0
if len(on1) > 0 and len(off1) > 0:
return 0
return 1
if len(on0) == 0 and len(off1) == 0:
return msb | solve2(i - 1, on1, off0)
if len(on1) == 0 and len(off0) == 0:
return msb | solve2(i - 1, on0, off1)
return min(solve2(i - 1, on0, off0), solve2(i - 1, on1, off1))
def solve(i, aaa):
if len(aaa) == 0:
return 0
if len(aaa) == 2:
return aaa[0] ^ aaa[1]
msb = 1 << i
on = []
off = []
for a in aaa:
if a & msb:
on.append(a ^ msb)
else:
off.append(a)
if i == 0:
return len(on) % 2
if len(on) % 2 == 0:
return max(solve(i - 1, on), solve(i - 1, off))
else:
return msb | solve2(i - 1, set(on), set(off))
n = int(input())
aaa = list(map(int, input().split()))
print(solve(29, aaa))
E - Increasing LCMs
問題
$N$ 個の整数 $A_1,A_2,...,A_N$(互いに異なる)
これらの整数を、以下の条件を満たすように並べ替える
可能か判定し、可能なら一例を示せ
$1 \le N \le 100$
解法
素因数分解とかしてしまいそうになるが、気付けば意外と簡単。
末尾から考えるとよい。
基本的に一番制約がきついのは末尾の数で、そこに置ける数があるなら貪欲に置いていってよい。
複数個候補があるならどれでもよい。
末尾に $A_i$ を置けるかどうかの判定は、
残っている整数から $A_i$ を除いた項の総LCMを $l$ とすると、$l$ が $A_i$ の倍数で無ければよい。
64bit Intの制約の中で解くなら、まず他の残っている全ての整数と $A_i$ とのGCDをそれぞれとり、その総LCMを取ればよい。
GCDを取ることで余計な素因数にフィルターをかけている感じなので、総LCMは $A_i$ を超えない。総LCMが $A_i$ と等しくなってしまったらNG、未満ならOK。
Python3
from math import gcd
def solve(aaa):
if len(aaa) == 1:
return [aaa[0]]
fwd = [1] * (len(aaa) + 1)
bwd = [1] * (len(aaa) + 1)
for i, a in enumerate(aaa):
fwd[i + 1] = fwd[i] * a // gcd(fwd[i], a)
for i in range(len(aaa) - 1, -1, -1):
bwd[i] = bwd[i + 1] * aaa[i] // gcd(bwd[i + 1], aaa[i])
i = 0
for i, a in enumerate(aaa):
others = fwd[i] * bwd[i + 1] // gcd(fwd[i], bwd[i + 1])
if others % a == 0:
continue
break
else:
print('No')
exit()
res = solve(aaa[:i] + aaa[i + 1:])
res.append(aaa[i])
return res
n = int(input())
aaa = list(map(int, input().split()))
ans = solve(aaa)
print('Yes')
print(*ans)
やけにPythonでACしてる人が多くて一瞬驚いたが、
多倍長を使える言語が有利な問題なため、
この問題だけPythonを使っていた、みたいな人が一定数いたためだった。