noyのブログ

プログラミングとかゲームとか

ARC067 - E Grouping

問題概要

N 人をグループ分けします。
一つのグループにつき A ~ B 人で構成し、
同じ人数のグループは 0 か C ~ D 個作らなければいけません。
このようなグループ分けは何通りありますか。

DPの解説

DPテーブルを書いてみます。

横軸が、グループ分けしていない、残っている人の数。
縦軸が、i 人以下のグループのみでグループ分けしていることを表します。

dp[i][j] = i 人以下のグループのみにでグループ分けした時、グループ分けされていない人の数が j 人の時の通り数
f:id:noy72:20171026131458j:plain:w500

入力は、3 1 2 1 2 だとします。

まず、0 人以下のグループのみでグループ分けした場合を考えます。
「誰も選べない」の 1 通りしかないので、dp[0][n] = 1。
残りの人数を減らせないので、その他はすべて0。

次に、1 人のグループのみでグループ分けした場合を考えます。
残り 3 人いるときに、1 人グループを 1 個作った場合、3 人のうちから 1 人選ぶので、 _3 C _1
f:id:noy72:20171026131501j:plain:w500

残り 3 人いるときに、1 人グループを 2 個作った場合、3 人のうちから 1 人選んで、 2 人のうちから 1 人選ぶ。
そして、重複を消せばよいので、 \frac{_3 C _1 \times _2 C _1}{2!}
f:id:noy72:20171026131505j:plain:w500

同じグループは 3 個以上作れないので、次に進みます。
残り2 人いるときに、1 人グループを 1 個作った場合……ですが、
dp[0][2] = 0 のため、dp[1][1] +=  _2 C _1 \times 0となります。
計算をしても +0 するだけなので省略。
f:id:noy72:20171026131509j:plain:w500

これで 1 人グループを作る場合をすべて試せました。
この計算結果は、1 人グループのみでグループ分けした結果の通り数を表しています。
f:id:noy72:20171026131514j:plain:w500

縦軸は、i 人以下のグループのみでグループ分けしていることを表すので、0 行の結果を加える必要があります。
f:id:noy72:20171026131523j:plain:w500

次に、2 人以下のグループのみでグループ分けした場合を考えます。
残り 3 人いるときに、2 人グループを 1 個作った場合、 _3 C _2
f:id:noy72:20171026131527j:plain:w500

2 人グループは 1個以上作れないので、次に進みます。
残り 2 人いるときに、2 人グループを 1 個作った場合、 _2 C _2
f:id:noy72:20171026131531j:plain:w500

2人グループを作る場合が終わりました。
f:id:noy72:20171026131535j:plain:w500

あとは先と同様に、1行の結果を加えます。
f:id:noy72:20171026131541j:plain:w500

これで本当に正しいのか、組み合わせを列挙してみます。

残り 3 人のとき
{ \phi } (空集合)
より 1 通り。

残り 2 人のとき
{1}
{2}
{3}
より 3 通り。

残り 1 人のとき
{1, 2}
{1, 3}
{2, 3}
{ {1}, {2} }
{ {1}, {3} }
{ {2}, {3} }
より 6 通り。

残り 0 人のとき
{ {1, 2}, {3} }
{ {1, 3}, {2} }
{ {2, 3}, {1} }
より 3 通り。

どうやら正しいようです。

実装

先に組み合わせ、階乗の逆元を計算しておきます。
あとは遷移させます。

計算結果がどんどん加算されていくDPなので、更新する方向に気をつければ 1 次元配列で計算できます。

残り 1000 人を 1 人グループに分ける場合、
1 個のとき: _{1000} C _1
2 個のとき: \frac{_{1000} C _1 \times _{999} C _1}{2!}
3 個のとき: \frac{_{1000 }C _1 \times _{999} C _1 \times _{998} C _1}{3!}
....
と組み合わせの計算に時間がかかるので、前回の計算結果を用いて計算量を落としています。

#include<bits/stdc++.h>
#define range(i,a,b) for(int i = (a); i < (b); i++)
#define rep(i,b) for(int i = 0; i < (b); i++)
#define all(a) (a).begin(), (a).end()
#define show(x)  cerr << #x << " = " << (x) << endl;
//const int INF = 1e8;
using namespace std;

const long long M = 1000000007;

long long nCr[1005][1005];
void Pascals(){
	nCr[0][0] = 1;
	range(i,1,1001){
		rep(j,i + 1){
			if(j == 0) nCr[i][j] = nCr[i - 1][j];
			else if(j == i) nCr[i][j] = nCr[i - 1][j - 1];
			else nCr[i][j] = (nCr[i - 1][j] + nCr[i - 1][j - 1]);
			nCr[i][j] %= M;
		}
	}
}

//べき乗 x^n mod M
long long power(long long x, long long n){
	long long res = 1;
	if(n > 0){
		res = power(x, n / 2);
		if(n % 2 == 0) res = (res * res) % M;
		else res = (((res * res) % M) * x ) % M;
	}
	return res;
}

//階乗
long long fact[1005];
long long factorial(long long n, long long r){
	long long res = 1;
	range(i,r,n + 1){
		res*= i;
		res%= M;
		fact[i] = res;
	}
	return res;
}

int main(){
	Pascals();
	factorial(1000,1);
	long long fact_rev[1005];
	rep(i,1001) fact_rev[i] = power(fact[i], M - 2);

	long long n, a, b, c, d;
	cin >> n >> a >> b >> c >> d;

	long long dp[1005] = {0};
	dp[n] = 1;
	range(i,a,b + 1){
		range(k,i * c,n + 1){
			long long person = k;
			long long comb = 1;

			range(j,1,d + 1){
				long long join = i * j;
				if(k - join < 0) break;

				(comb *= nCr[person][i]) %= M;
				person-=i;

				if(j >= c){
					(dp[k - join] += dp[k] * comb % M * fact_rev[j] % M) %= M;
				}
			}
		}
	}

	cout << dp[0] << endl;
}