版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/qq_35950004/article/details/83447105
必须写,太经典了。
题意:有来自n个专业的学生,每个专业分别有ai个同学,现在要将这些学生排成一行,使得相邻的两个学生来自不同的专业,问有多少种不同的安排方案。
思路就是隔板法。
设dp[i][j] 为前i个专业,有j对相邻的同系同学的方案数。
目标状态就是dp[n][0]
下面这个转移妙(厚)不(颜)可(无)言(耻)。
通过状态,我们可以发现应该一个系一个系的转移。dp[i-1] -> dp[i]
然后把一个系(i)的人分为k块,C(k-1 , cnt[i]) * k!
然后这k块中前h块隔开了原来相邻的同系同学。C(j,h) *C(sum +1 - j , k)
然后就可以转移了??
这里用Noi.ac 的 #97 Sequence的代码
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define maxn 31
#define mod 1234567891
#define LL long long
using namespace std;
int a[maxn],cnt[1005];
LL dp[2][maxn],C[maxn][maxn],fac[maxn];
int main()
{
int n,P;
scanf("%d%d",&n,&P);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]=(a[i]%P+P)%P,cnt[a[i]]++;
C[0][0]=1;
fac[0]=1;for(int i=1;i<maxn;i++) fac[i] = fac[i-1] * i % mod;
for(int i=1;i<maxn;C[i][0]=1,i++)
for(int j=1;j<=i;j++)
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
int now=1,pre=0,sum=0;
LL Fac=1;
dp[pre][0] = 1;
for(int i=0;i<P;i++)
if(cnt[i])
{
memset(dp[now],0,sizeof dp[now]);
for(int j=0;j<=sum;j++)
if(dp[pre][j])
for(int k=1;k<=cnt[i];k++)
for(int h=0;h<=j && h<=k;h++)
{
dp[now][j-h+cnt[i]-k] = (dp[now][j-h+cnt[i]-k] +
dp[pre][j] * C[j][h] % mod * C[sum+1-j][k-h] % mod * C[cnt[i]-1][k-1]) % mod;
}
swap(now,pre);
sum += cnt[i];
Fac = (Fac * fac[cnt[i]]) % mod;
}
printf("%lld\n",(dp[pre][0] * Fac)%mod);
}