题意:有来自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)+7; LL A[50]; LL C[500][500]; LL dp[50][500]; // dp[i][j]表示处理到第i组,一共还有j个位置左右坐的同学来自同一个专业 int n; int seq[50]; void pre() { A[0] = A[1] = 1; for (int i = 2; i < 50; ++i) { A[i] = A[i-1] * i % mod; } for (int i = 0; i < 500; ++i) { C[0][i] = 1; for (int j = 1; j <= i; ++j) { C[j][i] = (C[j][i-1] + C[j-1][i-1]) % mod; } } } int solve() { memset(dp, 0, sizeof (dp)); dp[1][seq[1]-1] = 1; // 给相邻同学来自一个系的间隙叫做粘着点 LL sum = seq[1]; for (int i = 2; i <= n; ++i) { for (int j = 0; j < sum; ++j) { // sum表示处理到前i-1组最多有sum个粘着点 for (int k = 1; k <= seq[i]; ++k) { // 枚举第i组同学被拆分成k个块放入到队伍中 for (int h = 0; h <= j && h <= k; ++h) { // 枚举有h个块放到了前面的j个粘着点,即破坏了粘着点,但显然块内带来了新的粘着点 dp[i][j-h+seq[i]-k] += dp[i-1][j]*C[h][j]%mod*C[k-h][sum+1-j]%mod*C[k-1][seq[i]-1]%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][0]; for (int i = 1; i <= n; ++i) { ret = ret * A[seq[i]] % mod; } return ret; } int main() { int T, ca = 0; pre(); scanf("%d", &T); while (T--) { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &seq[i]); } printf("Case %d: %d ", ++ca, solve()); } return 0; }