A,B,Dの三問でした。Cに悩みかけたけど、今までの経験から「まずは問題を全部読もう」と気づいてDを読んで、Dの方は単なる式変形で僕にとってやさしいからそっちを先に解いてからCに戻った。Cは正しそうな出力を生成できてるのだけど2件だけWAになって間に合わなかった。
やったー、水色になったよ!!
- 最後の10分くらいにドタバタしてた。 Dを40分弱で通して残り45分だったのだがバグを取りきれなくてCは通らず。まあDを先にやる判断が出来たのが僕の中での進歩か。
- 考えたこと
- AやBの取りうる値は対数オーダーなので実際のところいくつあるのか?
- 10^18以下の3^Aを数えてみる→37個
- じゃあ全探索で余裕だね python
def solve(N):
P3 = [3, 9, 27, 81, ...]
P5 = [5, 25, 125, 625, ...]
for i, x in enumerate(P3):
for j, y in enumerate(P5):
if x + y == N:
print(i + 1, j + 1)
return
print(-1)
- Aではなく3^Aをprintしてしまって1WA
- 考えたこと
- パスを通して値を移動できる
- 連結成分の中身の和が同じならOK
- UnionFindで連結成分を求めて、連結成分ごとのAとBの和を求める、一つでも差があればNo python
def solve(N, M, AS, BS, CD):
init_unionfind(N)
for (c, d) in CD:
unite(c - 1, d - 1)
sumA = defaultdict(int)
sumB = defaultdict(int)
for v in range(N):
root = find_root(v)
sumA[root] += AS[v]
sumB[root] += BS[v]
for k in sumA:
if sumA[k] != sumB[k]:
return "No"
return "Yes"
Cはしばらく眺めて手で具体例を作ったけど道筋が見えなかったので他の問題を読むことにした。Dが簡単そうだったのでDを解くことにした。
- 考えたこと
- 実装
- 素朴に書き下していく
- 2で割るところはmod Pでの逆元を掛ける
- 組み合わせ計算はmod Pでの組み合わせ
- 2回TLEしたのは前処理部分のケアレスミス
- 最初a ** xにしてて余りを取る前に桁が爆発し15TLE
- 次にpow(a, x, MOD)にしたが9TLE、しばらく頭を抱えた
- 上記は対数オーダー、毎回累乗しないで過去の結果を使いまわせば定数オーダーになる python
- 素朴に書き下していく
def solve(N, K, AS):
MOD = 998_244_353
div2 = pow(2, MOD - 2, MOD)
sumTable = [N] * (K + 2)
ps = AS[:]
for x in range(1, K + 1):
s = 0
for i in range(N):
s += ps[i]
s %= MOD
ps[i] *= AS[i]
ps[i] %= MOD
sumTable[x] = s
c = Comb(K + 1, MOD)
for x in range(1, K + 1):
ret = 0
for i in range(x + 1):
ret += c.comb(x, i) * sumTable[x - i] * sumTable[i]
ret %= MOD
p = pow(2, x, MOD)
ret -= sumTable[x] * p
ret %= MOD
ret *= div2
ret %= MOD
print(ret)
- まず素直に実装してランダム入力を叩き込んで観察します python
def aoki(LR):
selected = []
for lr1 in sorted(LR):
if not any(is_p(lr1, lr2) for lr2 in selected):
selected.append(lr1)
return len(selected)
def takahashi(LR):
from operator import itemgetter
selected = []
for lr1 in sorted(LR, key=itemgetter(1)):
if not any(is_p(lr1, lr2) for lr2 in selected):
selected.append(lr1)
return len(selected)
def random_test():
from random import seed, randint
for s in range(1000):
seed(s)
LR = []
for i in range(5):
W = 20
l = randint(0, W - 1)
r = randint(l + 1, W)
LR.append((l, r))
t = takahashi(LR)
a = aoki(LR)
if t != a:
print(s, LR, t, a)
- 観察
-
Mが負になることはなさそう
-
上はまで?(違います)
-
上はまでだな!(追記:違います)
-
観察結果の中に図Aみたいなのがあったので、あーなるほどね、ってBみたいな感じで生成することにしました
- 足りない部分の入れ方を忘れてたのでCに修正
-
大きいのを包み、細かい包まれてるものを子、最後に並んでるのを尻尾と呼ぶことにする
- Lでソートすると
- 包みが真っ先に来るので包まれてる子は排除される
- 尻尾も排除される
- →1
- Rでソートすると子が先に処理される
- 子の数はM個
- 尻尾の最初のものも追加される
- →全体でM+1
- これでMが達成できると思っていた
- MはN - 2までだと思い込んでたからね
- MはN - 1になりうるんじゃないか?と気づいて制約を緩めたが、N - 1の時には尻尾がないので「尻尾の最初のものが追加されて+1」が起きないからこのアルゴリズムで正しいデータを生成できない
- ここでコンテスト時間が尽きた
- Lでソートすると
-
- 公式解説
- MはN-1にはならない
- ただしN=1の場合を除く
- 僕のWAもそこの見落としだった
- N-1の時に-1を返していたのをやめた結果N1M0は通るようになったが上記のアルゴリズムが正しくない結果を作るので他のM=N-1のテストが通らなくなった
- MはN-1にはならない
- 反省点
- 残り10分くらいで完全にパニクってた
- 問題条件を見た時にNが1からなのは一瞬心に引っ掛かりがあったのだがスルーしてしまった
- 区間の交わりについて議論する時に、対象が1つしか存在しないってのは明らかな罠だった
- 境界値テスト
- 元ネタは区間スケジューリングで、知っていれば高橋解が最適解だとわかったそうだ