AtCoder Beginner Contest 206 D – KAIBUNsyo をPython3で解く

Share

AtCoder上にある問題のうち、AtCoder Problemsでdiff 800以上と判定されているものを順番に解いていく企画。
基本的な考え方は全てコード中のコメントに入れてあるので、参照のこと。

出典:
AtCoder Beginner Contest 206 D – KAIBUNsyo

この問題は緑色相当らしいけど、個人的には考察の部分が結構難しい部類だと思う……んだけど……みんなそんなパッと思いついてるのか……?

# AtCoder Beginner Contest 206 D - KAIBUNsyo
# https://atcoder.jp/contests/abc206/tasks/abc206_d
# tag: 考察 数列 回文 グラフ 連結成分 BFS DFS Union_Find

# a b c d e f g という数列と仮定して考えてみる。
# 回文にするためには、(a b c) と (g f e) を一致させる
# 必要がある。

# つまり、a-g b-f c-e に関してはそれぞれどちらかを
# 変更し、統一する必要があるということ。

# ところで、同じことを入力例1 について考えてみると、
# (1 5 3 2) と (1 3 2 5) を一致させることになり、
# 1-1 5-3 3-2 2-5 という対応が起こることになる。
# 上記の考察より、5-3 3-2 2-5 はそれぞれ同じ数字に
# 変換される必要がある。つまり、最終的に 5-3-2 全てが
# 同じ数字に変換されなければならないことになる。

# さて、N 種類の数字を全て同じ数字に変換するためには、
# 元の N 種類のうちのどれかひとつにあわせるとして、
# 最短でも N-1 回の変換を行う必要がある。

# 以上を踏まえると……
# 全ての対応を洗い出し、各数字を頂点としてグラフを構築。
# 連結成分ごとに、(要素数 - 1) 回の変換を行うことで
# 最終的な目標を達成できる。

def main():
    N = int(input())
    A = list(map(int, input().split()))

    # リストの前半とreversed(後半) を用意する。
    former = A[:N//2]
    latter = list(reversed(A[-N//2:]))

    # 先程のリストを元に、対応する数字一覧を作成する。
    path_dat = set()
    for f, l in zip(former, latter):
        path_dat.add((min(f, l), max(f, l)))

    # 一覧を元にグラフを構築。制約上限で作っておく。
    paths = [[] for _ in range(2 * 10**5 + 1)]
    for a, b in path_dat:
        paths[a].append(b)
        paths[b].append(a)

    result = 0

    visited = [False] * (2 * 10**5 + 1)

    # 連結成分ごとに探索を行う(BFSでもDFSでも良い)
    for start in range(2 * 10**5 + 1):
        if visited[start]:
            continue
        visited[start] = True
        queue = [start]

        # 連結成分の要素数
        cnt = 1

        # 探索開始(ここではDFS)
        while queue:
            now = queue.pop()
            for nxt in paths[now]:
                if visited[nxt]:
                    continue
                visited[nxt] = True
                cnt += 1
                queue.append(nxt)

        # 考察に従い、(要素数 - 1) を答えに加算
        result += cnt - 1

    print(result)

main()

# 上記の考察による回答は、連結成分を管理できればいいので、
# Union_Find によっても実装することができる。
# 特に連結成分数を管理できるように作っておくと楽。

class Union_Find:
    # 親管理リストと高さ管理リストを初期化し、
    # 要素N個のUnion-Find森を作成する。
    # 親管理リストは、基本的には自分のひとつ上の親を表すが、
    # 値が負の場合には、自身が最上位の親(リーダー)であることを表し、
    # 自分を含めたグループの人数を管理することとする
    def __init__(self, N):
        self.parent = [-1] * N
        self.rank = [0] * N
        self.group_count = N
        self.N = N

    # xの所属するグループのリーダーを返す
    def find(self, x):
        # 自分自身がリーダーなら、自分を返す
        if self.parent[x] < 0:
            return x

        # 再帰的に捜索し、見つかれば繋ぎ変えておく
        # (計算量が増える=面倒くさいので)高さ管理は行わない
        par = self.find(self.parent[x])
        self.parent[x] = par
        return par

    # xとyのグループを統合する
    def unite(self, x, y):
        # それぞれのリーダーに対する操作を行うことになる
        x = self.find(x)
        y = self.find(y)

        # リーダーが同じなら何もする必要がない
        if x == y:
            return (-1, -1)

        # 木の高さが同じ場合:
        # グループの人数を合計しつつ適当に繋ぎ、繋げられた方の高さを1増やす
        if self.rank[x] == self.rank[y]:
            self.parent[x] += self.parent[y]
            self.parent[y] = x
            self.rank[x] += 1
            self.group_count -= 1
            return (x, y)

        # 木の高さが違うなら、低い方を高い方につなぐ
        if self.rank[x] < self.rank[y]:
            x, y = y, x
        self.parent[x] += self.parent[y]
        self.parent[y] = x
        self.group_count -= 1
        return (x, y)

    # xとyが同じグループかどうかを調べる
    def samep(self, x, y):
        return self.find(x) == self.find(y)

### ここから main
def main2():
    N = int(input())
    A = list(map(int, input().split()))

    # リストの前半とreversed(後半) を用意する。
    former = A[:N//2]
    latter = list(reversed(A[-N//2:]))

    # 先程のリストを元に、対応する数字一覧を作成する。
    path_dat = set()
    for f, l in zip(former, latter):
        path_dat.add((min(f, l), max(f, l)))

    # 制約一杯で Union_Find木 を作成する。
    uft = Union_Find(2 * 10**5 + 1)

    # 対応する数字を連結していく。
    for a, b in path_dat:
        uft.unite(a, b)

    # 各連結成分の (要素数 - 1) の合計 =
    # 全体の要素数 - 連結成分の個数
    # となるので……
    print(2 * 10**5 + 1 - uft.group_count)

# main2()
Share

コメントを残す

メールアドレスが公開されることはありません。