目次

Keyence Programming Contest 2020 A~E問題メモ

Keyence Programming Contest 2020

ついに青に戻っちゃった。時間があればEまではできたけど、スピードに追いつけてない。

A - Painting

A - Painting

問題

解法

1回の操作で多く塗りたい。行、列の長い方を選ぶと1回で $\max(H,W)$ 個塗れる。$N$ を割って切り上げた値が答え。

h = int(input())
w = int(input())
n = int(input())
print((n - 1) // max(h, w) + 1)

B - Robot Arms

B - Robot Arms

問題

解法

ロボットを、$[X_i-L_i, X_i+L_i)$ を覆う1つの区間と見なすと、これは区間スケジューリング問題そのものである。

右端が左にある区間から順に、残せるなら貪欲に残していけばよい。

典型とはいえ、ABCなら200点ではまず出されない気はする。ただ、問題の配点は1つのコンテスト内での相対的な関係以外、あまり参考にしない方がよい。

import sys
from operator import itemgetter

n = int(input())
robots = []
for line in sys.stdin:
    X, L = map(int, line.split())
    l, r = X - L, X + L
    robots.append((l, r))
robots.sort(key=itemgetter(1))

curr = -1
ans = 0
for l, r in robots:
    if curr > l:
        continue
    ans += 1
    curr = r
print(ans)

C - Subarray Sum

C - Subarray Sum

問題

解法

問題Bとは打って変わって、思いつけば「え、そんなんでいいの」というような問題。

$0 \le K \le N$ という制約が鍵。つまり単独で $A_i=S$ であるような要素を $K$ 個作り、他の区間では和が $S$ にならないようにすれば、それで条件を満たす。

他の区緩和が $S$ にならないようにするには、たとえば $K$ 個以外の要素を $S$ より大きい数にすればいい。

ただし、$S=10^9$ の場合のみ、要素を $10^9$ より大きくはできないので、たとえば1にする。$N \le 10^5$ より、どんなに1が連続しても区間和が $10^9$ になることは無い。

n, k, s = map(int, input().split())
print(*([s] * k + [1 if s == 10 ** 9 else s + 1] * (n - k)))

D - Swap and Flip

D - Swap and Flip

問題

解法

$N$ も $A_i,B_i$ も小さい。

まず、手元で簡単な例を試すと、以下の点に気付く。

これで、カードを最終的に置く場所が決まれば、裏表の場合分けはしなくて済む。

しかし、最終位置は $N!$ 通りあるので、まだ全探索は不可能。動的計画法的に情報をまとめるか、枝刈りが必要。

最終位置を左から決めていくことを考える。

初期位置   1  2  3  4  5
最終位置   4  2  5  1  3

この最終位置に対する操作回数は転倒数であり、左から順に要素を確定させていった回数と一致する。 未確定のカードは初期位置順に並び、未確定の中で $k$ 番目のカードを左に持って行くには $k-1$ 回の操作がかかる。

4を移動    4 | 1   2   3   5    3回
2を移動    4   2 | 1   3   5    1回
5を移動    4   2   5 | 1   3    2回
1を移動    4   2   5   1 | 3    0回  => 計6回

左から $j$ 番目の位置まで確定していて、残りの部分からスコアを求めるには、どういった情報が必要か。

よって、以下のDPを考える。

これを、立っているbitの数が多い方から埋めていくと、$DP[0][num]$ の中で、最小の値を持つものが答えとなる。

計算量は、grpが $2^N$ 通り、$num$ も $A_i,B_i$ の値の種類数つまり最大 $2N$ 通りの状態があり、各遷移が $N$ かかるので、$O(N^2 2^N)$ となる。

一方、操作回数と最右の数字を入れ替えたこんなDPも作れるが、

こちらは、$op$ の状態数が大きくなりすぎるため、TLEとなる。 $A_i,B_i$ の範囲が小さいことを利用する。

def iter_bit(k, LIMIT):
    v = (1 << k) - 1

    while v < LIMIT:
        yield v
        x = v & -v
        y = v + x
        v = (v & ~y) // x >> 1 | y


def solve(n, cards):
    dp = [{} for _ in range(1 << n)]
    dp[-1][0] = 0
    LIMIT = 1 << n

    for d in range(n):
        for state in iter_bit(n - d, LIMIT):
            scores = dp[state]
            k = state
            curr = 0
            while k:
                b = k & -k
                i = b.bit_length() - 1
                num = cards[i][(d - i) % 2]
                next_dp = dp[state ^ b]
                for left_num, score in scores.items():
                    if left_num > num:
                        continue
                    ns = score + curr
                    if num not in next_dp or next_dp[num] > ns:
                        next_dp[num] = ns
                k ^= b
                curr += 1

    if len(dp[0]) > 0:
        return min(dp[0].values())
    else:
        return -1


n = int(input())
aaa = list(map(int, input().split()))
bbb = list(map(int, input().split()))
cards = list(zip(aaa, bbb))
print(solve(n, cards))

E - Bichromization

E - Bichromization

問題

解法

最小の $D_i$ を考えたとき、わりと可能な状態は少ないことに気付く。以下の例で、5が最小の $D_i$ であるとする。

b --- 5 --- a --- g --- f    数字,記号はDiを表す
       `--- d ---'           便宜上、頂点のこともDiで呼ぶ

異色の頂点に距離5でたどり着くには、当然、その最短路に含まれる辺には5以下の重みをつけなければならない。

(頂点5の条件を満たす塗り分け方と重みの設定例)
頂点  5 --- a --- g --- f
色    B     B     B     W
重み     1     3     1

しかし、5からまず最初に出る辺(5-a)を考えると、

なので、$a=5$ の時のみ、条件を満たす方法が存在する。

より広げていうと、ある頂点 $D_i$ に対してそこから直接繋がる頂点の内、最小のものを $D_j$、辺を $L_{i,j}$ とすると、

これを全ての頂点に対して繰り返す。矛盾する頂点が1つでもあると不可能。

サンプル1(丸付き数字はDi)
③--------④-----⑤-----⑦
 `---③---'
                              注目中の頂点を黒丸で示す。
❸--------④-----⑤-----⑦    ❸にとって、隣接中最もDjの小さい頂点は③
 3`--③---'                   よってその間の辺の重みを3に設定

③--------④-----⑤-----⑦    ❸にとっても同様
 3`--❸---'

③----4---❹-----⑤-----⑦    ❹にとっては隣接最小は③(2つあるが、どちらか一方)
 3`--③---'                   その間を4に設定

③----4---④--5--⑤--7--⑦    以下同様。
 3`--③---'                   ←の③-④を結ぶ辺は最後まで設定されなかった

この過程で重さが設定される辺数は、1頂点から1辺が設定されるので最大 $N$ 辺(重複する可能性はある)。 他の辺を最大値 $10^9$ にしておけば、とりあえず色を無視して「一番近い頂点に距離 $D_i$ で行ける」という距離の要件は満たす。

後は、その $N$ 辺の各辺について両端の頂点の色を逆にすると、条件を満たせる。

懸念点としては、$N$ 辺に奇数長の閉路が含まれると、隣り合う頂点同士が異なるように塗れない。 しかし、最も $D_j$ が小さい頂点を選んでいるため、閉路ができるのは閉路中の頂点が全て同じ $D$ を持つ場合しかありえない。 また、それも「$D_j$ が同じ場合は、辺番号(または頂点番号)が小さいものを優先する」とタイブレークを一意にしておけば防げるため、閉路の可能性は除外できる。

結果として $N$ 辺のみからなるグラフは森となり、連結成分ごとにDFSなどで隣り合う頂点を別にしながら色を設定していけばいい。

import sys
from operator import itemgetter


def solve(n, m, ddd, links):
    ddi = list(enumerate(ddd))
    ddi.sort(key=itemgetter(1))
    LIMIT = 10 ** 9 + 1
    costs = [LIMIT] * m
    for v, dv in ddi:
        du, li, u = min(links[v])
        if du > dv:
            print(-1)
            return
        costs[li] = dv

    colors = [-1] * n
    for i in range(n):
        q = [(i, 0)]
        while q:
            v, color = q.pop()
            if colors[v] != -1:
                continue
            colors[v] = color
            for du, li, u in links[v]:
                if costs[li] == LIMIT:
                    continue
                q.append((u, color ^ 1))

    MAX = 10 ** 9
    costs = [MAX if cost == LIMIT else cost for cost in costs]

    print(''.join('BW'[c] for c in colors))
    print('\n'.join(map(str, costs)))


n, m = map(int, input().split())
ddd = list(map(int, input().split()))
links = [[] for _ in range(n)]
for li, line in enumerate(sys.stdin):
    u, v = map(int, line.split())
    u -= 1
    v -= 1
    links[u].append((ddd[v], li, v))
    links[v].append((ddd[u], li, u))
solve(n, m, ddd, links)