HDU-4532 湫秋系列故事——安排座位 组合数学DP

题意:有来自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;
}
原文地址:https://www.cnblogs.com/Lyush/p/3418646.html