−目次
AtCoder Regular Contest 103
C - /\/\/\/
問題
- 数列 a1,a2,...,aNa1,a2,...,aN がある
- 長さ NN は偶数である
- これを {1,2,1,2,...}{1,2,1,2,...} のように、丁度2つの数字の繰り返しであるような数列にしたい
- 1つの数字を別の数字に変更するのを1手として、最小手数を求めよ
例
1 3 1 2 4 2 v v 1 2 1 2 1 2 に 2手で変更できる
1 1 1 1 1 1 v v v 1 2 1 2 1 2 数字は2種類でないといけない
解法
奇数番目と偶数番目、それぞれで数字の出現数を数えて、最も大きな数字を変更せず残すようにすると、手数は最小になる。
ただし、最頻の数字が同じ場合は、例の2つめのように別の数字にしなければいけないので、
「奇数番目の1番目と偶数番目の2番目」か「奇数番目の2番目と偶数番目の1番目」を比較して、手数が最小になる方を選ぶ。
奇/偶数番目に1種類の数字しか無かった場合、配列外参照を避けるための場合分けが少し面倒。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from collections import Countern = int(input())vvv = list(map(int, input().split()))cnt1 = Counter(vvv[0::2])cnt2 = Counter(vvv[1::2])mc1 = cnt1.most_common(2)mc2 = cnt2.most_common(2)if mc1[0][0] == mc2[0][0]: sc1 = mc1[1][1] if len(mc1) > 1 else 0 sc2 = mc2[1][1] if len(mc2) > 1 else 0 print(min(n - mc1[0][1] - sc2, n - sc1 - mc2[0][1]))else: print(n - mc1[0][1] - mc2[0][1]) |
D - Robot Arms
問題
- ロボットアームを構築する
- ロボットアームは mm 本の腕が m+1m+1 個の関節で繋がれた構造
- 関節にアームで繋がれた順に 0,1,...,m0,1,...,m と番号を付けると、関節0は原点(0,0)(0,0)に固定
- 各アームはxx軸またはyy軸に平行に伸ばす
- つまり、ii 本目のアームの長さが didi、その一方の関節 ii が(xi,yi)(xi,yi)にある場合、もう一方の関節は以下のいずれかになる
'U': (xi,yi+di)(xi,yi+di)'D': (xi,yi−di)(xi,yi−di)'R': (xi+di,yi)(xi+di,yi)'L': (xi−di,yi)(xi−di,yi)
- 原点の反対側の端をぴったり合わせたい目標が NN 点あり、ii 番目の目標は(Xi,Yi)(Xi,Yi)
- いずれの目標にも、関節 mm を合わせられるようなロボットアームを構築せよ
- アームの本数 mm
- 各アームの長さ d1,d2,...,dmd1,d2,...,dm
- 各目標に合わせるためのアームの配置 s1,s2,...,sNs1,s2,...,sN
- sisi は長さ mm の文字列で、
'UDRL' のいずれかの文字からなる
- 出来ない場合は
-1を出力せよ - 1≤N≤10001≤N≤1000
- −109≤Xi,Yi≤109−109≤Xi,Yi≤109
- 1≤m≤401≤m≤40
解法
可能性判定
まず、できるできないの判定として、全ての Xi+YiXi+Yi の偶奇は一致していないといけない。
つまり、座標を市松模様に塗り分けると、アームの動かし方より関節mm は、黒なら黒、白なら白のみにしか行けない。偶奇が混じっていたら-1。
それさえ満たしていれば可能なのかはまだ分からないが、ひとまず先に進む。
アームの本数と長さ
構築問題では、2通りの考え方が出来て、
- 与えられた条件から出発して、ぴったり合うように作る
- どんな条件が来ても対応できるアームの本数と長さが存在するのでそれを見つける
なのだが、以下の当て推量より、後者で考えてみる。
- 「組み合わせの合計によって満遍なくどんな数にも対応」には2のべき乗がよく使われる
- 座標の範囲が 109≃230109≃230 で、微調整に何本か使うとして、アームの本数の制約=40の「ねらい」が何となく感じられる
よって、Xi+YiXi+Yi が奇数なら{1,2,4,8,...,231}{1,2,4,8,...,231}、偶数なら1で微調整するとして {1,1,2,4,8,...,231}{1,1,2,4,8,...,231} のアームを用意してみる。
各目標へ
アームのカバー範囲を考えてみる。
長さ1のアームの一端が ① にあるとき、もう一端は4方向のいずれかをカバーできる。
・ ・①・ ・
長さ2→長さ1の順に2本のアームを考えると、16点をカバーできる。これらの点は、ナナメ正方形の範囲内で漏れはない。
・
・①・
・ ・ ・
・①・②・①・
・ ・ ・
・①・
・
4→2→1の3本を考えると、64点をカバーできる。これも、ナナメ正方形内の点は網羅できている。
\・①・②・①・/
・\・ ・ ・/・
・①・\・①・/・①・
・ ・ ・\・/・ ・ ・
・①・②・①・④・①・②・①・
・ ・ ・/・\・ ・ ・
・①・/・①・\・①・
・/・ ・ ・\・ (一部)
/・①・②・①・\
同様に考えると、原点からアームの長い順に考えて、目標点がナナメに4分割した上下左右の正方形のいずれに入るか、ということを再帰的に貪欲に求めていけばよい。
これで Xi+YiXi+Yi が奇数の場合は求められ、偶数の場合も、長さ1のアームでずらして考えると奇数に帰着できる。
E - Tr/ee
問題
- 長さ nn の文字列 ss が与えられる
- ss は
'0'または'1'のみからなる - 以下の条件を満たす頂点数 nn の木を構築せよ
- 木の任意の辺を1つだけ選び、そこで分割して連結成分を切り出すことを考えた時、
- ss の ii 文字目が
'1'のとき、頂点数 ii の連結成分を切り出せるような辺が存在する - ss の ii 文字目が
'0'のとき、頂点数 ii の連結成分を切り出せるような辺は存在しない
- 2≤n≤1052≤n≤105
解法
ss の末尾は'0'でないといけない。1つの辺で分割する以上、最低1つの頂点は切り離されるので、頂点数 nn の連結成分にはなり得ない。
ss の1文字目は'1'でないといけない。木には必ず葉があるので、それに繋がる辺で分割すれば、頂点数1の連結成分は必ず作れる。
ss の ii 文字目と n−in−i 文字目は一致してないといけない。頂点数 nn の木から ii を切り出すと、もう一方の頂点数は n−in−i である。
以上の3点をまずチェックする。
次に、連結成分が作れる時と作れない時がどういう状況か、考える。
頂点数Kが作れる場合
...... -o--o----o- ......
/ ↑ここで分割
...... --o
~~~~~~~~~~~~
あわせてK個の頂点
頂点数Kが作れない場合
o
/
...... ----o----o- ......
↑ここで分割してもK+1個になり、ダメ
~~~~~~~~~~~~
K個の頂点(上の1点は含まない)
構築
ある1点を葉としてそこから「メインの1本」「条件を満たすための調整としての葉」のいずれかを1点ずつ繋いでいくことを考える。
最終的にこんな感じになる。(数字は繋ぐ順)
1 2 3 7 9 10 13
o--o--o----o--o--o----o-- ...
/|\ | / \
o o o o o o
4 5 6 8 11 12
左から考える。
頂点3まで来て、ss の3文字目が0だったとする。すると、頂点数3の連結成分を作らせないために、頂点3にはあと2点、繋ぐ必要がある。
1点のみしか繋がなければ、その1辺で分割することで頂点数3の連結成分に出来てしまう。逆に2点以上繋ぐと(少なくとも頂点1から繋がる部分では)頂点数3の連結成分には分割できなくなる。
これを踏まえて、k+1k+1 点目の頂点を繋ぐときは以下のようにするとよい。
- s[k]=1s[k]=1なら、メインの1本を伸ばし、自身がメインの先頭となる
- s[k]=0s[k]=0なら、枝葉として、メインの現時点の先頭に繋げる
考えるのは n+12n+12 点目まででよい。それ以降は全てメインの先頭に繋げてしまえばよい。
ooo
o--o--o----o--o--o----o-o
/|\ | / \ ooo
o o o o o o
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 |
def solve(s): if s[-1] == '1': return False if s[0] == '0': return False half = s[:len(s) // 2] if half != s[-2:(len(s) - 3) // 2:-1]: return False head = 1 master = 1 buf = [] for i, c in enumerate(half): if c == '0': buf.append('{} {}'.format(master, i + 2)) else: buf.append('{} {}'.format(head, i + 2)) head = master = i + 2 for i in range(len(half), len(s) - 1): buf.append('{} {}'.format(master, i + 2)) return bufres = solve(input())if res == False: print(-1)else: print('\n'.join(res)) |
F - Distance Sums
問題
- 長さ NN の数列 D1,D2,...,DND1,D2,...,DN
- DiDi の値はすべて異なる
- 以下の条件を満たす NN 頂点の木を構築する
- 各頂点には 1,2,...,N1,2,...,N の番号
- 各辺の長さは1
- 各頂点 ii に対して、ii から他の頂点までの距離の和は DiDi
- 存在しなければ
'-1'を出力
解法
ある隣接する2点 u,vu,v の Du,DvDu,Dv について考える。
.. -o--o, ,o--o- .. -o--o- u -- v -o--o- .. .. -o--o' `o--o- ~~~~~~~~~~~ ~~~~~~~~~~~ 頂点数 ku 頂点数 n-ku
DuDu と DvDv の差は一意に決まる。uu 側の部分木に属する頂点数を kuku とする。
uu から vv に移ると、kuku 個の頂点については距離が1ずつ増え、(n−ku)(n−ku) 個の頂点については1ずつ減る。よって、Dv=Du+ku−(n−ku)=Du−(n−2ku)Dv=Du+ku−(n−ku)=Du−(n−2ku)
これを踏まえると、kiki の小さい、つまり葉に近い頂点の方が DiDi は大きくなり、逆に中央付近の頂点が小さくなる。
DiDi が最小の頂点 rr を根として考える。
rr 以外の各 ii について、kiki が確定すれば、親が一意に決定でき、木が構築できる。(Dp=Di−(n−2ki)Dp=Di−(n−2ki) である pp が親)
kiki を確定させるには、全ての子頂点について先んじて処理すれば、ki=(子頂点のkの合計)+1 となる。
子のD は親のD より確実に大きいため、Di が大きい方から処理すればよいと分かる。頂点 i に処理が回ってきた時点で、既にそれより葉に近い子頂点の処理は完了し、ki は確定している。
Di が最大の頂点は確実に葉であり、葉の部分木の頂点数は明らかに1であるため、最初はそこから始める。
- Di が大きい順に、頂点 i の親を確定させていく
- ki が確定しているため、親の Dp が計算でき、p が特定できる
- Dp である頂点 p が存在しない場合は、構築は不可能である
ただし、これはあくまで差分についてしか見ていないため、最後に Dr を調べ、実際に合致しているかチェックする。真値 D′r は、構築中に計算できる。
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 |
def solve(n, ddd): dsd = {d: i for i, d in enumerate(ddd)} child_cnt = [1] * n # including self child_dist = [0] * n buf = [] srt = sorted(dsd.items(), reverse=True) for d, i in srt[:-1]: cc = child_cnt[i] pd = d - (n - cc * 2) if pd == d or pd not in dsd: return -1 pi = dsd[pd] buf.append((pi + 1, i + 1)) child_cnt[pi] += cc child_dist[pi] += child_dist[i] + cc md, mi = srt[-1] if md != child_dist[mi]: return -1 return bufn = int(input())ddd = list(map(int, (input() for _ in range(n))))res = solve(n, ddd)if res == -1: print(-1)else: print('\n'.join('{} {}'.format(*l) for l in res)) |

