题意:有来自n个专业的学生,每个专业分别有ai个同学,现在要将这些学生排成一行,使得相邻的两个学生来自不同的专业,问有多少种不同的安排方案。
分析:首先将所有专业的学生视作一样的,最后再乘以各自学生的数量的阶乘。排列的时候通过动态规划来处理,设状态为前i个系,一共有j个位置相邻位置来自同系,然后转移。具体见代码注释。
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std; typedef long long LL;
const LL mod = (int)(1e9)+;
LL A[];
LL C[][];
LL dp[][]; // dp[i][j]表示处理到第i组,一共还有j个位置左右坐的同学来自同一个专业
int n;
int seq[]; void pre() {
A[] = A[] = ;
for (int i = ; i < ; ++i) {
A[i] = A[i-] * i % mod;
}
for (int i = ; i < ; ++i) {
C[][i] = ;
for (int j = ; j <= i; ++j) {
C[j][i] = (C[j][i-] + C[j-][i-]) % mod;
}
}
} int solve() {
memset(dp, , sizeof (dp));
dp[][seq[]-] = ; // 给相邻同学来自一个系的间隙叫做粘着点
LL sum = seq[];
for (int i = ; i <= n; ++i) {
for (int j = ; j < sum; ++j) { // sum表示处理到前i-1组最多有sum个粘着点
for (int k = ; k <= seq[i]; ++k) { // 枚举第i组同学被拆分成k个块放入到队伍中
for (int h = ; h <= j && h <= k; ++h) {
// 枚举有h个块放到了前面的j个粘着点,即破坏了粘着点,但显然块内带来了新的粘着点
dp[i][j-h+seq[i]-k] += dp[i-][j]*C[h][j]%mod*C[k-h][sum+-j]%mod*C[k-][seq[i]-]%mod;
// C[h][j]表示h个快插入了哪些粘着点
// C[k-h][sum-1-j]表示k-h个块插入了那些非粘着点,总间隙是sum+1个
// C[k][seq[i]-1]表示这seq[i]个同学是如何划分成k个块的
dp[i][j-h+seq[i]-k] %= mod;
}
}
}
sum += seq[i];
}
LL ret = dp[n][];
for (int i = ; i <= n; ++i) {
ret = ret * A[seq[i]] % mod;
}
return ret;
} int main() {
int T, ca = ;
pre();
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = ; i <= n; ++i) {
scanf("%d", &seq[i]);
}
printf("Case %d: %d\n", ++ca, solve());
}
return ;
}