[学習記録]AtCoder EDP O-Matching [Go]

問題

N 人の男性たちと N 人の女性たちがいます。 男性たちには1,2,…,N と番号が振られています。 同様に、女性たちには 1,2,…,N と番号が振られています。

各 i,j (1≤i,j≤N) について、男性 i と女性 j の相性の良し悪しが整数 ai,j​ によって与えられます。 ai,j​=1 ならば男性 i と女性 j は相性が良く、ai,j​=0 ならば相性が悪いです。

太郎君は、相性が良い男女どうしのペアを N 組作ろうとしています。 このとき、各男性および各女性はちょうど 1 つのペアに属さなければなりません。

N 組のペアを作る方法は何通りでしょうか? 109+7 で割った余りを求めてください。

解法

i 番目の男性 × j 番目の女性でマッチするかを, i,  j  を状態として持ち求めようとすると
「i 番目の男性 × j 番目の女性でマッチ」の状態に遷移する前の場合の数がうまく計算できない。
BitDPを使うことでこれをうまく計算できる
・BitDP 集合をビット列で表し、それを添え字として持つDPのこと
    e.g. { 1, 0, 0, 1 } => dp[1001] = dp[2^3 + 20] = dp[9]
i 番目の男性と j 番目の女性、j 番目の女性のマッチの有無を持つ集合 s を考える。

n := readInt()

sli := make([][]int, n)
for i := 0; i < n; i++ {
	sli[i] = readSli(n)
}

// dp = [マッチした女性の集合をビットで表したもの]その時の場合の数
// n人のデータを持つ必要があるため、1をnビットシフトさせる
// n = 3 のとき、 1<3 = 1000, { 111(3人の集合) } + 1を保持できる
dp := make([]int, 1<<n)
dp[0] = 1

集合 s について、すべてのパターンを見ていく。
何番目の男性かは、そのときにたっているビットの数を数える。
マッチした場合、その時の場合の数をマッチした後の s を持つDPに加える


for s := 0; s < 1<<n; s++ {
    i := bits.OnesCount(uint(s))
	for j := 0; j < n; j++ {
		if sli[i][j] == 0 {
			continue
		}

    // j 番目のビットを立てる = j ビットシフトさせ、 s とのビット論理和をとる
		dp[s|1<<j] += dp[s]
		dp[s|1<<j] %= prime_number
	}
}

s となる場合の数が0の場合、その状態は起きない
 = それ以後の状態に影響を与えないのでスキップ。
また、j 番目の女性がすでにマッチ済みの場合もスキップ。

for s := 0; s < 1<<n; s++ {
    // 0 のとき、それ以後の状態に影響を与えない
	if dp[s] == 0 {
		continue
	}

	i := bits.OnesCount(uint(s))
	for j := 0; j < n; j++ {
        // マッチ済みの場合
        // e.g. s = 100101, j=2 のとき
        //   s>>j = 1001, s>>j & 1 = 1
		if s>>j&1 == 1 {
			continue
		}

		if sli[i][j] == 0 {
			continue
		}

		dp[s|1<<j] += dp[s]
		dp[s|1<<j] %= prime_number
	}
}

すべて計算したあと、DPの、すべてのビットが1の添え字のもの
 = 1 << n -1 が答えになる

// e.g n = 3 のとき
//   1 << n = 1000
//   1 << n -1 = 0111
ans := dp[1<<n-1]

(参考)例題1の、i = 1 までの動き

// 3
// 0 1 1
// 1 0 1
// 1 1 1

// s=0 => i=0,
//  1行目が0 1 1なので、
//    0 マッチしない
//    1 => s[010], dp[010] +=1
//    1 => s[001], dp[001] +=1
// s=1 => i=1,
//  2行目が1 0 1なので
//    1 => s[101], dp[101] += dp[001]
//    0 => マッチしない
//    1 => 既にマッチ済み
// s=2 => i=1 = 010
//  2行目が1 0 1なので
//    1 => s[110], dp[110] += dp[001]
//    0 => マッチしない
//    1 => s[011], dp[011] += dp[001]

コード(AC)

n := readInt()

	sli := make([][]int, n)
	for i := 0; i < n; i++ {
		sli[i] = readSli(n)
	}

	// dp = [マッチした女性の集合をビットで表したもの]その時の場合の数
	dp := make([]int, 1<<n)
	dp[0] = 1

	for s := 0; s < 1<<n; s++ {
		if dp[s] == 0 {
			continue
		}

		i := bits.OnesCount(uint(s))
		for j := 0; j < n; j++ {
			if s>>j&1 == 1 {
				continue
			}

			if sli[i][j] == 0 {
				continue
			}

			dp[s|1<<j] += dp[s]
			dp[s|1<<j] %= prime_number
		}
	}

	ans := dp[1<<n-1]
	return ans


この記事が気に入ったらサポートをしてみませんか?