C♯の勉強

C♯4.0 で TopCoder の過去問を解きます。

TopCoder SRM600: PalindromeMatrix

回文になる行の集合を全通り試す。

回文になる行の箇所が固定されると、(0列とW-1列), (1列とW-2列)... のペアそれぞれについて独立に処理できるため、あとは動的計画法で処理できる。

本番だと、i列と(W-1-i)列のペアについて、「i列が回文である / でない」×「(W-1-i列)が回文である / でない」の4通りについて、PalindromeMatrixDiv2 と同様に連結情報を求めて処理させようとしたけれど、実装が重くて間に合わなかった。

以下の解では、「i列が回文である」と「W-1-i列が回文である」を状態にした動的計画法を使って計算している。

public class PalindromeMatrix {
    static public void Swap<T>(ref T a, ref T b) { T t = a; a = b; b = t; }
    static public void UpdateMin<T>(ref T x, T newValue) where T : IComparable<T> { if (x.CompareTo(newValue) > 0) x = newValue; }
    static public void Fill<T>(T[] array, T value) { for (int i = 0; i < array.Length; i++) array[i] = value; }

    static public bool NextPermutation<T>(T[] array) where T : IComparable<T> {
        int n = array.Length;
        for (int i = n - 2; i >= 0; i--) {
            if (array[i].CompareTo(array[i + 1]) < 0) {
                var index = Array.FindLastIndex(array, v => array[i].CompareTo(v) < 0);
                Swap(ref array[i], ref array[index]);
                Array.Reverse(array, i + 1, n - (i + 1));
                return true;
            }
        }
        Array.Reverse(array);
        return false;
    }
    int R, C;
    string[] A;
    int?[,] memo;
    bool[] perm;

    int dfs(int firstPos, int lastPos, int columnCount) {
        if (columnCount <= 0) columnCount = 0;
        if (firstPos >= lastPos) {
            if (columnCount == 0) return 0;
            return 1 << 28;
        }
        if (!memo[firstPos, columnCount].HasValue) {
            int val = 1 << 28;

            var dp = new int[4];
            var next = new int[4];

            for (int r1 = 0, r2 = R - 1; r1 < r2; r1++, r2--) {
                Fill(next, 1 << 28);
                // r11 ... r12
                // r21 ... r22
                for (int i = 0; i < 4; i++) {
                    for (int k = 0; k < 16; k++) {
                        var r11 = ((k & 1) != 0 ? 1 : 0) + '0';
                        var r12 = ((k & 2) != 0 ? 1 : 0) + '0';
                        var r21 = ((k & 4) != 0 ? 1 : 0) + '0';
                        var r22 = ((k & 8) != 0 ? 1 : 0) + '0';

                        if (r11 != r21 && (i & 1) != 0) continue;
                        if (r12 != r22 && (i & 2) != 0) continue;
                        if (perm[r1] && r11 != r12) continue;
                        if (perm[r2] && r21 != r22) continue;

                        int change = 0;
                        if (r11 != A[r1][firstPos]) change++;
                        if (r12 != A[r1][lastPos]) change++;
                        if (r21 != A[r2][firstPos]) change++;
                        if (r22 != A[r2][lastPos]) change++;

                        UpdateMin(ref next[i], change);
                    }
                    dp[i] += next[i];
                    if (dp[i] >= (1 << 28)) dp[i] = 1 << 28;
                }
            }

            for (int i = 0; i < 4; i++) {
                int remainCount = columnCount;
                if ((i & 1) != 0) remainCount--;
                if ((i & 2) != 0) remainCount--;
                UpdateMin(ref val, dp[i] + dfs(firstPos + 1, lastPos - 1, remainCount));
            }

            memo[firstPos, columnCount] = val;
        }
        return memo[firstPos, columnCount].Value;
    }

    public int minChange(string[] A, int rowCount, int columnCount) {
        this.A = A;
        this.R = A.Length;
        this.C = A[0].Length;

        this.perm = new bool[R];
        for (int i = 0; i < rowCount; i++) {
            perm[i] = true;
        }
        Array.Sort(perm);
        int ans = int.MaxValue;
        do {
            memo = new int?[20, 20];
            ans = Math.Min(ans, dfs(0, C - 1, columnCount));
        } while (NextPermutation(perm));
        return ans;
    }
}