目次
AtCoder Grand Contest 041 A~C問題メモ
A - Table Tennis Training
問題
- $2N$ 人が $N$ 卓で卓球を繰り返し行う
- 卓は順に $1~N$ のランクがついている
- ランク $i$ の卓で試合を行った2人のうち、勝者は $i-1$、敗者は $i+1$ へ移って次の試合を行う
- ただし、卓1の勝者は卓1に留まり、卓 $N$ の敗者は $N$ に留まる
- 今、友人同士である2人がそれぞれ卓 $A,B$ にいる
- 2人が出会うまでに必要な最小の試合数(2人同士の対戦は含まない)を求めよ
- $2 \le N \le 10^{18}$
解法
このような移動では、基本的に2人の距離の偶奇は変わらない。$A-B$ は偶数ならずっと偶数、奇数ならずっと奇数である。
この距離を0にしたい。
偶数なら、ランクの小さい方が負け続け、大きい方が勝ち続ければ2ずつ縮まり、$\frac{|A-B|}{2}$ が答え。
厄介なのは奇数の時で、卓1で勝つか、卓 $N$ で負けることでのみ、2人の距離の偶奇が調整される。
- 最初にランクが小さい方が卓1で勝ってから負け続ける(大きい方は勝ち続ける)
- 最初にランクが大きい方が卓 $N$ で負けてから勝ち続ける(小さい方は負け続ける)
この2つの両方を試し、小さい方を答えればよい。
def solve(n, a, b):
if a > b:
a, b = b, a
if (b - a) % 2 == 0:
return (b - a) // 2
top = (b + a - 1) // 2
c, d = n - a - 1, n - b + 1
boo = (c + d + 1) // 2
return min(top, boo)
n, a, b = map(int, input().split())
print(solve(n, a, b))
B - Voting Judges
問題
- $N$ 問の問題があり、投票で $P$ 問を選ぶ
- 既にいくらかの票は入っており、$i$ 番目の問題には $A_i$ 票入っている
- 残り $M$ 人のジャッジが投票する
- ジャッジはそれぞれ独立に、好きな $V$ 問を(重複無く)選び、それに1票ずつ入れる
- $M$ 人全ての投票が終わった後、得票数の上位 $P$ 問が選ばれる。同率の場合は任意に選ばれる
- $N$ 問の内、選ばれる可能性のある問題の数を求めよ
- $2 \le N \le 10^5$
- $1 \le V,P \le N-1$
解法
$A_i$ は、あらかじめ降順にソートしておき、改めてindexを振り直しておく。
最初から上位 $P$ 位以内の問題は、$M$ 人が全てその問題に投票すれば勝ち残れる。
それより下位の問題が選ばれるような状況を考える。
畢竟……這い上がれないッ……! 誰かを蹴落とさなければ……現在の上位 $P$ 問の……誰かをッ……!
狙われるは……必然ッ……! そう、最も弱い問題 $P$ !!
$M$ 人全てから得票しても$A_P$ に届かない問題は、選ばれる可能性が無い。
その間にある問題 $i$($A_P-M \le A_i \lt A_P$)については、少し考える必要がある。
M=5 P=3
Ai 10 9 8 | 6 5 3 | 2 1
最初から | ? ? ? | 5人全員が投票し、3位以内の問題に誰も投票しなくても
上位P以内| | その中の最低"8"に勝てないので敗退確定
問題 $i$ は、$M$ 人全てから得票するとして、最終得票可能数は $A_i+M$ となる。
問題 $i$ が選ばれるためにライバルとなる問題は、$P ~ i-1$ である。 $1~P-1$ とは競っても仕方ないし、$i+1~N$ には必ず勝てる。
Ai 10 9 | 8 6 | 5 | 3 2 1
争わ | rival| i | M人のジャッジの票が全て入っても、
ない| | | (同じく全ての票が入った) 5に勝つことは無い
↓ ↓ →M票獲得して問題ない
M票獲得して問題ない
ライバル全員に勝つか同点で終われば、選ばれる可能性はある。 ライバル以外にはどれだけ票が入ってもいい、もっと言うとライバルに入らないよう、票を喰ってくれた方がありがたい。
ライバル以外の問題に、$M$ 人全てが投票するのが最適である。このような問題の数を $s$ とする。
$s \ge V$ であれば、ライバルには1票も入らないことが可能なので、通過できる。
そうで無い場合、ライバルのどれかに、ジャッジ1人あたり $V-s$ 票、合計 $M(V-s)$ 票入る。
ライバルも、$A_i+M$ 票までなら獲得してよい。これを「猶予」とすると、ライバル全員の猶予の合計が $M(V-s)$ 以下であればよい。
M=5
rival i
票 8 6 5
10 ・ ・ □ ■: 初期状態の得票(Ai)
9 ・ ・ □ □: iが最大可能な得票
8 ■ ・ □ ・: 猶予
7 ■ ・ □
6 ■ ■ □
5 ■ ■ ■
...
1問あたりの得票が $M$ 以下であるという制約はあるが、今回の場合、その制約は必ず満たされるので考慮しなくてよい。
from collections import Counter
def solve(n, m, v, p, aaa):
aaa.sort(reverse=True)
count = Counter(aaa)
scores = sorted(count.keys(), reverse=True)
tmp_cnt = 0
for pi, s in enumerate(scores):
c = count[s]
tmp_cnt += c
if tmp_cnt >= p:
ans = tmp_cnt
rival = s
htm_cnt = tmp_cnt - (p - 1) # higher than me excluding top p-1
htm_sum = s * htm_cnt
break
else:
raise RuntimeError
# print(ans)
# print(rival)
# print(htm_cnt, htm_sum)
for s in scores[pi + 1:]:
c = count[s]
upper = s + m
if upper < rival:
break
v4mtm = v - (n - htm_cnt) # remaining vote for higher than s excluding top p-1
if v4mtm <= 0:
ans += c
else:
space = upper * htm_cnt - htm_sum
if space >= m * v4mtm:
ans += c
htm_cnt += c
htm_sum += s * c
return ans
n, m, v, p = map(int, input().split())
aaa = list(map(int, input().split()))
print(solve(n, m, v, p, aaa))
C - Domino Quality
問題
- $N \times N$ のマス目状の盤面があり、ここにいくつかのドミノを置いていく
- ドミノは縦または横に、隣り合う2マスをぴったり覆うように置く
- 重ねては置けない
- 以下の条件を満たすような置き方を1つ構築せよ。無理ならその旨を示せ
- ある列(行)について「その列(行)の1マス以上を占めるドミノの枚数」を、その列(行)の「クオリティ」とする
- 全ての列・行でクオリティが等しい
- $2 \le N \le 1000$
解法
小さい盤面の組み方が分かれば、それをつなぎ合わせれば任意の盤面を作れるので、小さい盤面を全探索しましょう、という問題。
偶然上手くいくことはあっても、理屈立てて成立する盤面を見つけるのは難しい(?)。
たとえば3×3と4×4で共通するクオリティを持つ盤面を作ることができれば、7×7の盤面を作れる。同様にこれを繰り返すと、6,8,9,10,… などどんどん広げられる。
■■■・・・・ ■■■・・・・ ■■■・・・・ ・・・■■■■ ・・・■■■■ ・・・■■■■ ・・・■■■■
なので、小さめのいくつかの $N$ で共通するクオリティを持つ盤面を見つけたい。
ところで、ドミノ1枚置くと行+列のクオリティの総和は3増える。
011 0・・・ 1・□□ 0・・・
なので、3の倍数で無い $N$ で成立するクオリティがあるとすれば、それは3の倍数であるはずである。
小さい盤面なので6以上は考えづらいので、「共通するクオリティは3」と固定して、$N=2~8$ あたりを探索する。
すると、4以上の全てで可能であることが見つかる。
def search(field, xs, ys, n, q, c, r):
if r == 0:
return field
for i in range(n):
if xs[i] >= q:
continue
can_vertical = i < n - 1 and xs[i + 1] < q
for j in range(n):
if ys[j] >= q:
continue
if field[i][j] != 0:
continue
if can_vertical and field[i + 1][j] == 0:
field[i][j] = field[i + 1][j] = c
xs[i] += 1
xs[i + 1] += 1
ys[j] += 1
result = search(field, xs, ys, n, q, c + 1, r - 1)
if result is not None:
return result
field[i][j] = field[i + 1][j] = 0
xs[i] -= 1
xs[i + 1] -= 1
ys[j] -= 1
if j < n - 1 and ys[j + 1] < q and field[i][j + 1] == 0:
field[i][j] = field[i][j + 1] = c
xs[i] += 1
ys[j] += 1
ys[j + 1] += 1
result = search(field, xs, ys, n, q, c + 1, r - 1)
if result is not None:
return result
field[i][j] = field[i][j + 1] = 0
xs[i] -= 1
ys[j] -= 1
ys[j + 1] -= 1
return None
def search_main(n, q=3):
print(n, q)
field = [[0] * n for _ in range(n)]
xs = [0] * n
ys = [0] * n
remain = n * 2 * q // 3
result = search(field, xs, ys, n, q, 1, remain)
if result is None:
print(None)
else:
for row in result:
print(' '.join('{:02d}'.format(x) for x in row))
search_main(3)
search_main(4)
search_main(5)
search_main(6)
search_main(7)
4の盤面をナナメに繰り返し、4~7で最後の1つの埋め方を調整すれば、$N$ が大きくなっても4以上の全てで可能であることがわかる。
また、$N=3$ については個別に可能であるパターンがある。(これはまぁ手探りでも見つかる)
def solve(n):
if n <= 2:
print(-1)
return
if n == 3:
print('aab\nc.b\ncdd')
return
field = [['.'] * n for _ in range(n)]
r = n % 4
repeat = [n, n - 5, n - 6, n - 7]
for i in range(0, repeat[r], 4):
field[i + 0][i:i + 4] = 'aacd'
field[i + 1][i:i + 4] = 'bbcd'
field[i + 2][i:i + 4] = 'efgg'
field[i + 3][i:i + 4] = 'efhh'
if r == 1:
field[n - 5][n - 5:n] = 'abc..'
field[n - 4][n - 5:n] = 'abc..'
field[n - 3][n - 5:n] = 'deeff'
field[n - 2][n - 5:n] = 'd.ghh'
field[n - 1][n - 5:n] = 'iigjj'
elif r == 2:
field[n - 6][n - 6:n] = 'abc...'
field[n - 5][n - 6:n] = 'abc...'
field[n - 4][n - 6:n] = 'd.eeff'
field[n - 3][n - 6:n] = 'dghh..'
field[n - 2][n - 6:n] = '.g.ijj'
field[n - 1][n - 6:n] = 'kk.ill'
elif r == 3:
field[n - 7][n - 7:n] = 'abc....'
field[n - 6][n - 7:n] = 'abc....'
field[n - 5][n - 7:n] = 'def....'
field[n - 4][n - 7:n] = 'def....'
field[n - 3][n - 7:n] = 'g..hhii'
field[n - 2][n - 7:n] = 'g..jjkk'
field[n - 1][n - 7:n] = '.llmmnn'
print('\n'.join(map(''.join, field)))
n = int(input())
solve(n)

