Official

F - Make Pair Editorial by mechanicalpenciI


この問題は区間 DP によって解くことができます。

区間 DP は区間を添え字として持って行う動的計画法で、今回の問題のように例えば右端の添え字だけ等で動的計画法を行うのが難しく、また最低でも \(2\) 乗の計算量を許す制約の時に使えることが多いです。

今回の問題では

\[ dp[i][j]=(生徒 i+1 , 生徒 i+2 , \ldots 生徒 i+2j から j 組のペアを選ぶ方法 ) \]

として定めてあげれば良いです。答えは\(dp[0][N]\) です。 他にも添え字を左端の生徒番号、右端の生徒番号という形などにしても構いません。

さて、 \(j\) について昇順に\(dp[i][j]\) の値を求めることを考えます。 更新の仕方としてはいくつかありますが、過不足ないように数え上げなければならないことに注意してください。 例えば、生徒 \(i+1\)がペアとなるような相手ごとに足し合わせていけばよいです。偶奇の関係から生徒 \(i+1\) は生徒 \(i+2k\) と書ける相手としかペアにならないことに注意しましょう。 生徒 \(i+1\) , 生徒 \(i+2\) , \(\ldots\) , 生徒 \(i+2j\) から \(j\) 組のペアを選ぶ方法 のうち、生徒 \(i+1\) と生徒 \(i+2k\) がペアとなるような場合の数を考えます。

まず前の \(2k\) 人と後ろの \(2(j-k)\) 人はそれぞれのグループの中の人とペアを組むことになります。生徒 \(i+1\) , \(\ldots\) , 生徒 \(i+2k\) からの選び方の場合の数は生徒 \(i+1\) と生徒 \(i+2k\) の仲が悪い場合は \(0\) 通り、仲が良い場合は生徒 \(i+2\) , \(\ldots\) , 生徒 \(i+2k-1\) から \(k-1\) 組のペアを選んでから最後に生徒 \(i+1\) と生徒 \(i+2k\) がペアとなる方法しかないので、 \(k-1\) 組のペアの選び方に等しく \(dp[i+1][k-1]\) 通りとなります。 一方、生徒 \(i+2k+1\) , \(\ldots\) , 生徒 \(i+2j\) から \(j-k\) 組のペアを選ぶ方法が \(dp[i+2k][j-k]\) 通りで、最後に順序として生徒 \(i+1\) , \(\ldots\) , 生徒 \(i+2k\) と生徒 \(i+2k+1\) , \(\ldots\) , 生徒 \(i+2j\) のどちらから選ぶかの順序は自由に決められるから、

\[ dp[i][j]=\sum_{k=1}^j (dp[i+1][k-1]\times dp[i+2k][j-k]\times {}_jC_k \times \mathrm{friends}[i+1][i+2k]) \]

として求まります。ただし、\(\mathrm{friends}[i][j]\) は生徒 \(i\) と生徒 \(j\) の仲が良いならば \(1\) , 悪いならば \(0\) です。 初期状態は \(dp[0][0]=dp[1][0]=\cdots =dp[2n][0]=1\) として 上の式を使って更新していけば解は求まります。

この時、各更新は \(O(N)\) の時間計算量でできるので、全体で \(O(N^3)\) となり、十分間に合います。よって、この問題を解く事が出来ました。

C++による実装例:

#include <bits/stdc++.h>

using namespace std;

#define N 410
#define ll long long
#define MOD 998244353
#define rep(i, n) for(int i = 0; i < n; ++i)

int main(void) {
	int n, m;
	int a, b;
	ll x, y, z;
	bool can[N][N];
	ll dp[N][(N / 2)];
    ll binom[N][N];
	binom[0][0] = 1;
	for (int i = 1; i < N; i++) {
		binom[i][0] = 1;
		binom[i][i] = 1;
		rep(j, i - 1) {
			binom[i][j + 1] = (binom[i - 1][j] + binom[i - 1][j + 1]) % MOD;
		}
	}

	cin >> n >> m;
	rep(i, 2 * n)rep(j, 2 * n)can[i][j] = false;
	rep(i, m) {
		cin >> a >> b;
		can[a - 1][b - 1] = true;
		can[b - 1][a - 1] = true;
	}
	rep(i, 2 * n + 1)dp[i][0] = 1;
	for (int j = 1; j <= n; j++) {
		rep(i, 2 * (n - j) + 1) {
			dp[i][j] = 0;
			rep(k, j) {
				if (can[i][i + (2 * k) + 1]) {
					x = (dp[i + 1][k] * dp[i + (2 * k) + 2][j - k - 1]) % MOD;
					dp[i][j] = ((x*binom[j][k + 1]) + dp[i][j]) % MOD;
				}
			}
		}
	}
	cout << dp[0][n] << endl;

	return 0;
}

posted:
last update: