Jzoj P4254 集体照___计数dp

版权声明:欢迎借鉴,谢绝抄搬。 https://blog.csdn.net/Gx_Man_VIP/article/details/86708590

题目大意:

n n 个班(从 1 1 开始编号),每个班的人数为 a i a_i ,现在他们要排集体照,有 2 2 个要求。
①所有学生站一排。
②相邻两个学生不能同班。
问满足的排列的方案总数,答案对 1 e 9 + 7 1e9+7 取模

1 < = n < = 50 , 1 < = a i < = 50 , i = 1 n a i < = 1500 1<=n<=50,1<=a_i<=50, \sum_{i=1}^{n}a_i<=1500

分析:

考虑计数 d p dp
d p i , j dp_{i,j} 表示前 i i 个班,有 j j 个空隙是两边同班的排列的方案总数。
此时我们不考虑同班内人的不同,我们把一个班内的当成同类人,最后的结果只需要乘上 i = 1 n a i ! \prod_{i=1}^{n}a_i! 即可,因为每个班在满足序列合法的情况下可以由不同的排列方式。
初值: d p 1 , a 1 1 = 1 dp_{1,a_1-1}=1
假如第 i + 1 i+1 个班,我将其分为 k k 组,用其中的 l l 组人去插入这 j j 个空隙,
这时我令 s u m i sum_i 为前 i i 个班的人数之和
则可以推出转移方程:
d p [ i + 1 ] [ j + ( a i + 1 1 ) ( k 1 ) l ] + = d p [ i ] [ j ] C j l C a i + 1 1 k 1 C s u m i + 1 j k l dp[i+1][j+(a_{i+1}-1)-(k-1)-l]+=dp[i][j]*C_{j}^{l}*C_{a_{i+1}-1}^{k-1}*C_{sum_{i}+1-j}^{k-l}
j + ( a i + 1 1 ) ( k 1 ) l j+(a_{i+1}-1)-(k-1)-l ,加入了 a i + 1 a_{i+1} 个同班人以后,多了 a i + 1 1 a_{i+1}-1 个满足两边同班的空隙,将其分为 k k 组,此时少了 ( k 1 ) (k-1) 个满足两边同班的空隙,用 l l 组人去分别在 j j 个满足两边同班的空隙中选 l l 个隔开,此时少了 l l 个满足两边同班的空隙
C j l C_{j}^{l} j j 个空隙中任选 l l 个插入的方案数
C a i + 1 1 k 1 C_{a_{i+1}-1}^{k-1} a i + 1 a_{i+1} 个数分成 k k 组的方案数(插板法)
C s u m i + 1 j k l C_{sum_{i}+1-j}^{k-l} :在剩余位置中插入 k l k-l 组数,即 s u m i + 1 sum_i+1 个空隙中分别(左右两边也行)插入 k l k-l 组数的方案数

最后的答案就是 d p n , 0 i = 1 n a i ! dp_{n,0}*\prod_{i=1}^{n}a_i!

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <cstring>
#include <algorithm>

#define M 1505
#define N 55

using namespace std;

typedef long long ll;

const int modn = 1e9 + 7;

ll dp[N][M], C[M][M], sum[N], jc[M], a[N], mul;
int n;

void Pre_Work()
{
	jc[1] = 1;
	for (int i = 2; i <= 1500; i ++) jc[i] = (ll)jc[i - 1] * i % modn;
	C[0][0] = 1;
	for (int i = 1; i <= 1500; i ++)
	{
		C[i][0] = 1;
		for (int j = 1; j <= i; j++) C[i][j] = ((ll)C[i - 1][j - 1] + C[i - 1][j]) % modn;
	}
}

int main(){
	freopen("photo.in", "r", stdin);
	freopen("photo.out", "w", stdout);
	scanf("%d", &n);
	Pre_Work();
	mul = 1;
	for(int i = 1; i <= n; i++)
		scanf("%lld", &a[i]), 
		sum[i] = sum[i - 1] + a[i], mul = mul * jc[a[i]] % modn;
		
	dp[1][sum[1] - 1] = 1; 
	for (int i = 1; i < n; i++)
		for (int j = 0; j < sum[i]; j++) 
		    if (dp[i][j])
			   for (int k = 1; k <= a[i + 1]; k++)
			      for (int l = 0; l <= min(k, j);  l++)
				      dp[i + 1][j + a[i + 1] - k - l] = 
					    ((dp[i + 1][j + a[i + 1] - k - l] % modn + dp[i][j] * C[j][l] % modn * C[a[i + 1] - 1][k - 1] % modn * C[sum[i] - j + 1][k - l]) % modn + modn) % modn;
    printf("%lld\n", ((dp[n][0] * mul) % modn + modn) % modn);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Gx_Man_VIP/article/details/86708590