#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL __int64
using namespace std;
const LL MOD=1000000007ll;
LL C[500][500];
LL Nn[500];
LL dp[50][500]; //表示前i个系,存在j个空位,它左右两个同学是来自同一个系的种数。此时是把同一个系的同学看作是无区别的
//等计算完毕后再乘上阶乘即可。
int stu[50];
/*此题确实是妙,开始时就往容斥想,但很复杂,没能做出来。
题解是利用了是否存在相邻两个同学来自同一个系这个特点来设状态。
*/
void Init(){
Nn[1]=1;
for(int i=2;i<=500;i++)
Nn[i]=(Nn[i-1]*(LL)i)%MOD;
memset(C,0,sizeof(C));
C[0][0]=1ll;
for(int i=1;i<=500;i++){
for(int j=0;j<=i;j++){
if(j==0) C[i][j]=1ll;
else if(j==i) C[i][j]=1ll;
else{
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
}
}
}
void slove(int n){
dp[1][stu[1]-1]=1;
int sum=stu[1];
for(int i=2;i<=n;i++){
for(int j=0;j<sum;j++){//前i个系最多有sum个空位(左右来自同一个系的),于是枚举这些空位
for(int k=1;k<=stu[i];k++){//加入第i个系的同学,把这i个同学分块。
for(int h=0;h<=j&&h<=k;h++){//表示前h个块的同学加入到这j个空位中
dp[i][j-h+stu[i]-k]+=(((dp[i-1][j]*C[j][h])%MOD*C[stu[i]-1][k-1])%MOD*C[sum+1-j][k-h])%MOD;
//C[j][h]表示从j个空位中选出h个让其加入
// C[stu[i]-1][k-1]表示如何分块
//C[sum+1-j][k-h]表示将剩余的块加入非j个空位,即左右来自不同系的
//dp[i][j-h+stu[i]-k]当h个空位加入了同学,导致左右同系的空位减少。但明显的,分块的块内同样引入了
//左右同系的空位
dp[i][j-h+stu[i]-k]%=MOD;
}
}
}
sum+=stu[i];
}
LL ans=dp[n][0];
for(int i=1;i<=n;i++){
ans=(ans*Nn[stu[i]])%MOD;
}
printf("%I64d\n",ans);
}
int main(){
Init();
int T;
scanf("%d",&T);
int n,t=0;
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&stu[i]);
memset(dp,0,sizeof(dp));
printf("Case %d: ",++t);
slove(n);
}
return 0;
}
原文:http://www.cnblogs.com/jie-dcai/p/4367114.html