Processing math: 100%

AtCoder Regular Contest 103

C - /\/\/\/

問題

  • 数列 a1,a2,...,aN がある
  • 長さ N は偶数である
  • これを {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 Counter
 
n = 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

問題

  • ロボットアームを構築する
  • ロボットアームは m 本の腕が m+1 個の関節で繋がれた構造
  • 関節にアームで繋がれた順に 0,1,...,m と番号を付けると、関節0は原点(0,0)に固定
  • 各アームはx軸またはy軸に平行に伸ばす
    • つまり、i 本目のアームの長さが di、その一方の関節 i(xi,yi)にある場合、もう一方の関節は以下のいずれかになる
      • 'U': (xi,yi+di)
      • 'D': (xi,yidi)
      • 'R': (xi+di,yi)
      • 'L': (xidi,yi)
  • 原点の反対側の端をぴったり合わせたい目標が N 点あり、i 番目の目標は(Xi,Yi)
  • いずれの目標にも、関節 m を合わせられるようなロボットアームを構築せよ
    • アームの本数 m
    • 各アームの長さ d1,d2,...,dm
    • 各目標に合わせるためのアームの配置 s1,s2,...,sN
      • si は長さ m の文字列で、'UDRL' のいずれかの文字からなる
  • 出来ない場合は-1を出力せよ
  • 1N1000
  • 109Xi,Yi109
  • 1m40

解法

可能性判定

まず、できるできないの判定として、全ての Xi+Yi の偶奇は一致していないといけない。

つまり、座標を市松模様に塗り分けると、アームの動かし方より関節m は、黒なら黒、白なら白のみにしか行けない。偶奇が混じっていたら-1

それさえ満たしていれば可能なのかはまだ分からないが、ひとまず先に進む。

アームの本数と長さ

構築問題では、2通りの考え方が出来て、

  • 与えられた条件から出発して、ぴったり合うように作る
  • どんな条件が来ても対応できるアームの本数と長さが存在するのでそれを見つける

なのだが、以下の当て推量より、後者で考えてみる。

  • 「組み合わせの合計によって満遍なくどんな数にも対応」には2のべき乗がよく使われる
  • 座標の範囲が 109230 で、微調整に何本か使うとして、アームの本数の制約=40の「ねらい」が何となく感じられる

よって、Xi+Yi が奇数なら{1,2,4,8,...,231}、偶数なら1で微調整するとして {1,1,2,4,8,...,231} のアームを用意してみる。

各目標へ

アームのカバー範囲を考えてみる。

長さ1のアームの一端が ① にあるとき、もう一端は4方向のいずれかをカバーできる。

  ・
・①・
  ・

長さ2→長さ1の順に2本のアームを考えると、16点をカバーできる。これらの点は、ナナメ正方形の範囲内で漏れはない。

      ・
    ・①・
  ・  ・  ・
・①・②・①・
  ・  ・  ・
    ・①・
      ・

4→2→1の3本を考えると、64点をカバーできる。これも、ナナメ正方形内の点は網羅できている。

      \・①・②・①・/
      ・\・  ・  ・/・
    ・①・\・①・/・①・
  ・  ・  ・\・/・  ・  ・
・①・②・①・④・①・②・①・
  ・  ・  ・/・\・  ・  ・
    ・①・/・①・\・①・
      ・/・  ・  ・\・    (一部)
      /・①・②・①・\

同様に考えると、原点からアームの長い順に考えて、目標点がナナメに4分割した上下左右の正方形のいずれに入るか、ということを再帰的に貪欲に求めていけばよい。

これで Xi+Yi が奇数の場合は求められ、偶数の場合も、長さ1のアームでずらして考えると奇数に帰着できる。

E - Tr/ee

問題

  • 長さ n の文字列 s が与えられる
  • s'0'または'1'のみからなる
  • 以下の条件を満たす頂点数 n の木を構築せよ
    • 木の任意の辺を1つだけ選び、そこで分割して連結成分を切り出すことを考えた時、
    • si 文字目が'1'のとき、頂点数 i の連結成分を切り出せるような辺が存在する
    • si 文字目が'0'のとき、頂点数 i の連結成分を切り出せるような辺は存在しない
  • 2n105

解法

s の末尾は'0'でないといけない。1つの辺で分割する以上、最低1つの頂点は切り離されるので、頂点数 n の連結成分にはなり得ない。

s の1文字目は'1'でないといけない。木には必ず葉があるので、それに繋がる辺で分割すれば、頂点数1の連結成分は必ず作れる。

si 文字目と ni 文字目は一致してないといけない。頂点数 n の木から i を切り出すと、もう一方の頂点数は ni である。

以上の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まで来て、s の3文字目が0だったとする。すると、頂点数3の連結成分を作らせないために、頂点3にはあと2点、繋ぐ必要がある。

1点のみしか繋がなければ、その1辺で分割することで頂点数3の連結成分に出来てしまう。逆に2点以上繋ぐと(少なくとも頂点1から繋がる部分では)頂点数3の連結成分には分割できなくなる。

これを踏まえて、k+1 点目の頂点を繋ぐときは以下のようにするとよい。

  • s[k]=1なら、メインの1本を伸ばし、自身がメインの先頭となる
  • s[k]=0なら、枝葉として、メインの現時点の先頭に繋げる

考えるのは n+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 buf
 
 
res = solve(input())
if res == False:
    print(-1)
else:
    print('\n'.join(res))

F - Distance Sums

問題

  • 長さ N の数列 D1,D2,...,DN
  • Di の値はすべて異なる
  • 以下の条件を満たす N 頂点の木を構築する
    • 各頂点には 1,2,...,N の番号
    • 各辺の長さは1
    • 各頂点 i に対して、i から他の頂点までの距離の和は Di
  • 存在しなければ'-1'を出力

解法

ある隣接する2点 u,vDu,Dv について考える。

.. -o--o,        ,o--o-
.. -o--o- u -- v -o--o- ..
.. -o--o'        `o--o-
~~~~~~~~~~~    ~~~~~~~~~~~
 頂点数 ku       頂点数 n-ku

DuDv の差は一意に決まる。u 側の部分木に属する頂点数を ku とする。

u から v に移ると、ku 個の頂点については距離が1ずつ増え、(nku) 個の頂点については1ずつ減る。よって、Dv=Du+ku(nku)=Du(n2ku)

これを踏まえると、ki の小さい、つまり葉に近い頂点の方が Di は大きくなり、逆に中央付近の頂点が小さくなる。

Di が最小の頂点 r を根として考える。

r 以外の各 i について、ki が確定すれば、親が一意に決定でき、木が構築できる。(Dp=Di(n2ki) である p が親)

ki を確定させるには、全ての子頂点について先んじて処理すれば、ki=(k)+1 となる。

子のD は親のD より確実に大きいため、Di が大きい方から処理すればよいと分かる。頂点 i に処理が回ってきた時点で、既にそれより葉に近い子頂点の処理は完了し、ki は確定している。

Di が最大の頂点は確実に葉であり、葉の部分木の頂点数は明らかに1であるため、最初はそこから始める。

  • Di が大きい順に、頂点 i の親を確定させていく
    • ki が確定しているため、親の Dp が計算でき、p が特定できる
    • Dp である頂点 p が存在しない場合は、構築は不可能である

ただし、これはあくまで差分についてしか見ていないため、最後に Dr を調べ、実際に合致しているかチェックする。真値 Dr は、構築中に計算できる。

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] * # 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 buf
 
 
n = 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))

programming_algorithm/contest_history/atcoder/2018/0929_arc103.txt · 最終更新: 2018/10/13 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0