SRM517D1H Colorful Board

考察成分が大きい?

問題概要(原文)

$N \times M$ のマス目がある.最終的に各マスの目標の色が与えられる.達成する最小手数を求めよ.不可能なら $-1$ を返せ.

  • 操作1: 列と色を選ぶ.その列に属するマス目のマスを全て選んだ色で塗る
  • 操作2: 行と色を選ぶ.その行に属するマス目のマスを全て選んだ色で塗る

考察

少なくとも各列,各行は $1$ 度だけ操作すればよい.つまり,答えの最大値の上限は $N+M$ である.

ここで,使う必要がない列(あるいは行)が必ず存在するはずである.そうでないとすると,最初の $1$ 回目に選んだ操作は全て無駄になってしまう.答えの最大値の上限は $N+M-1$ であることがわかった.

これはかなり嬉しい.なぜなら,操作されない列/行があるとすると,行/列に対する操作が全て分かる.すると,各行,各列にどの色を割り当てたかを求めることができる.割当ができないなら,その列/行を操作しないで達成することは不可能である.

$i$ 行 $j$ 列の色において,行の色を先に塗らないといけないなら $n+j \rightarrow i$ へ,列の色を先に塗らないといけないなら, $i \rightarrow n+j$ へ有向辺を張る.これがDAGになっていないならば,達成不可能である.

最小の手数は,行,列において,塗る色が未確定の頂点の数で定まる.

計算量は $O((N+M)^4)$

ソースコード

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Debug = System.Diagnostics.Debug;
using StringBuilder = System.Text.StringBuilder;
public class ColorfulBoard
{
    public int theMin(string[] s)
    {
        var ans = 100000;
        var n = s.Length;
        var m = s[0].Length;
        for (int k = 0; k < n + m; k++)
        {
            var ok = true;
            var R = Enumerate(n, x => '?');
            var C = Enumerate(m, x => '?');
            if (k < n) for (int j = 0; j < m; j++) C[j] = s[k][j];
            else for (int i = 0; i < n; i++) R[i] = s[i][k - n];

            for (int i = 0; i < n; i++)
                for (int j = 0; j < m; j++)
                {
                    if (s[i][j] == C[j]) continue;
                    if (s[i][j] == R[i]) continue;
                    if (R[i] == '?') R[i] = s[i][j];
                    if (C[j] == '?') C[j] = s[i][j];
                }

            var g = new bool[n + m, n + m];
            for (int i = 0; i < n + m; i++)
                g[i, i] = true;
            for (int i = 0; i < n; i++)
                for (int j = 0; j < m; j++)
                {
                    if (s[i][j] != C[j] && s[i][j] != R[i]) ok = false;
                    if (R[i] == C[j]) continue;
                    if (s[i][j] == R[i]) g[n + j, i] = true;
                    else g[i, n + j] = true;
                }
            for (int kk = 0; kk < n + m; kk++)
                for (int i = 0; i < n + m; i++)
                    for (int j = 0; j < n + m; j++)
                        g[i, j] |= g[i, kk] & g[kk, j];
            for (int i = 0; i < n + m; i++)
                for (int j = i + 1; j < n + m; j++)
                    if (g[i, j] & g[j, i]) ok = false;
            if (ok)
                ans = Math.Min(ans, R.Count(x => x != '?') + C.Count(x => x != '?'));

        }
        if (ans > n + m) return -1;
        return ans;
    }
    static public T[] Enumerate<T>(int n, Func<int, T> f) { var a = new T[n]; for (int i = 0; i < n; ++i) a[i] = f(i); return a; }
}

コメント

  • 考察を理解してからバグらせまくったのは反省