4bitにっき

ぽよ

JOI2013春合宿Day2 マスコットの片付け (Mascots)

問題

http://www.ioi-jp.org/camp/2013/2013-sp-tasks/2013-sp-day2.pdf]
マスコットを縦R*横Cの長方形の領域が埋まるように置いていく。
ただし、N個のマスコットは既に置かれている。(位置は与えられる)
置いていく途中でマスコットの集合が長方形になったら幸せなので、そのようにしたい。
置いていく途中でマスコットの集合が最多回長方形になるようなマスコットの置き方は何通りあるか。


解法

まず最多回長方形が作れる置き方を考えます。
最初はすべてのマスコットを含む最小の長方形を作ります。
この通り数は単純に順列で求まります。
その長方形から4方向に長方形を1だけ拡張する置きかたを繰り返していけば、必ず最多回長方形ができます。
4方向に長方形を1だけ拡張するとき、マスコットの置き方は
 (拡張する方向に対しての幅)!
そこで、
 dp[i][j][k][l] = 上にi回,下にj回,右にk回,左にl回拡張したときの \\ \hspace{8em} マスコットの置き方の通り数
というDPを考えます。でもこれは最悪計算量がO(R^2C^2)だから、明らかに間に合いません。

ここで、上に拡張するときと下に拡張するとき、右に拡張するときと左に拡張するときはそれぞれ拡張する方向に対しての幅がどんなときも同じであることを利用して、まとめて計算できないか考えます。
和の法則で
 {マスコットの置き方の総数 = \\ \hspace{8em} \sum_{}^{} {(各拡張のしかたに対してのマスコットの置き方の通り数)}}
とします。
例を考えてみましょう。

R = 3, C = 3
○○○
○×○
○×○
(×はマスコット)

拡張の仕方を矢印を使って表すと、
各拡張の仕方とそれに対してのマスコットの置き方の通り数は、
(拡張の仕方) : 通り数
{
↑ → ← : 1!*3!*3! \\
↑ ← → : 1!*3!*3! \\
→ ↑ ← : 2!*2!*3! \\
→ ← ↑ : 2!*2!*3! \\
← ↑ → : 2!*2!*3! \\
← → ↑ : 2!*2!*3!
}

さっきの考察から、上に拡張しようが下に拡張しようが(同様に右と左も)右側の順列には関係ありません。
そこで縦に拡張するのを|、横に拡張するのを-として表すと
{
 | - -  : 1!*3!*3! * 2 \\
 - | -  : 2!*2!*3! * 2 \\
 - - |  : 2!*2!*3! * 2
}

例えば|--だと↑→←、↑←→の2つの拡張の仕方が含まれていますが、これは組み合わせを使って計算で出せます。
u = 上に拡張できる回数、d = 下に拡張できる回数、r = 右に拡張できる回数、l = 左に拡張できる回数 とおくと、
 \large {{}_{u + d} C_u * {}_{r + l} C_r}
組み合わせはパスカルの三角形を利用して前計算しておけばよいです。

あとはDPを使って右側の順列部分も前計算しておきます。
 dp[i][j] = 縦にi回、横にj回拡張するときのマスコットの置き方の通り数
漸化式は
 \left\{
\begin{array}{l}
dp[0][0] = 1 \\
dp[i][j] = dp[i - 1][j] * j! + dp[i][j - 1] * i!
\end{array}
\right.
すると答えは
 \large {(最初の長方形を作る通り数(順列)) * dp[u + d][r + l] * {}_{u + d} C_u * {}_{r + l} C_r}
計算量は最初の順列計算がO(RC)パスカルの三角形の前計算がO(RC)、DPがO(RC)(事前の階乗計算が必要)なので、全体としてもO(RC)です。

コード

using namespace std;
 
typedef long long ll;
 
#define int long long
 
const int MOD = 1e9 + 7;
 
int R, C, N;
int dp[3002][3002], memo[3001], combination[3001][3001];
 
int Factorial(int n)
{
	if (n > 3000) return Factorial(n - 1) * n % MOD;
	if (memo[n] != 0) return memo[n];
	return memo[n] = Factorial(n - 1) * n % MOD;
}
 
signed main()
{
	memo[0] = 1;
	combination[0][0] = 1;
 
	pair<int, int> sr(LLONG_MAX / 2, 0), sc(LLONG_MAX / 2, 0);
	cin >> R >> C >> N;
	for (int i = 0; i < N; i++)
	{
		int a, b;
		cin >> a >> b;
		sr.first = min(sr.first, a);
		sr.second = max(sr.second, a);
		sc.first = min(sc.first, b);
		sc.second = max(sc.second, b);
	}
	int ans = Factorial((sr.second - sr.first + 1) * (sc.second - sc.first + 1) - N);
	
	for (int i = 0; i < max(R, C); i++)
	{
		for (int j = 0; j <= i; j++)
		{
			combination[i + 1][j] = (combination[i + 1][j] + combination[i][j]) % MOD;
			combination[i + 1][j + 1] = (combination[i + 1][j + 1] + combination[i][j]) % MOD;
		}
	}
 
	dp[sr.second - sr.first + 1][sc.second - sc.first + 1] = 1;
	for (int i = sr.second - sr.first + 1; i <= R; i++)
	{
		for (int j = sc.second - sc.first + 1; j <= C; j++)
		{
			if (dp[i][j] > 0) continue;
			dp[i][j] = dp[i - 1][j] * Factorial(j) + dp[i][j - 1] * Factorial(i);
			dp[i][j] %= MOD;
		}
	}
	int nr = R - (sr.second - sr.first + 1), kr = sr.first - 1;
	int nc = C - (sc.second - sc.first + 1), kc = sc.first - 1;
	ans = ans * dp[R][C] % MOD;
	ans = ans * combination[nr][kr] % MOD;
	ans = ans * combination[nc][kc] % MOD;
	printf("%lld\n", ans);
	return 0;
}