ZJNU 2455 - Zapina (DP)
COCI 2019/2020 CONTEST #5 - Zapina
题意
给\(n\)个人分配\(n\)种不同的工作。
\(n\)个人拍成一列,第\(i\)个人只有在被严格安排到\(i\)份工作时会感到快乐(不会在意是什么工作,只在意数量)。
问至少有一人感到快乐的分配方案数量。
限制
\(1\leq n\leq 350\)
思路
考虑对立事件,事件“至少有一人快乐”的对立事件为“没有一个人感到快乐”
由于每份工作之间是相互独立的,所以安排方案总共有\(n^n\)种
所以所求可以转化成“总方案数”减去“没有一个人感到快乐”的方案数
考虑没有一个人感到快乐的情况,进行动态规划
令\(dp[i][j]\)表示\(i\)个人分配\(j\)份工作,“没有一个人感到快乐”的方案数
单独考虑最后一个人被安排到的工作数量\(k\),容易得到转移方程
\[dp[i][j]=dp[i][j]+C_{j}^{k}dp[i-1][j-k],\ k\neq i
\]
表示\(i\)个人分配\(j\)份工作,可以由\(i-1\)个人分配\(j-k\)份工作转移而来,且第\(i\)个人被分配到的这\(k\)份工作可以用组合数\(C_j^k\)来得出方案数
注意这里的\(k\neq i\)的条件,表示第\(i\)个人不能分配到\(i\)份工作,否则就偷税了
最后,从小到大枚举\(i,j,k\)即可
人数枚举区间\([1,n]\)
工作可以没有,枚举区间\([0,n]\)
第\(i\)个人的工作需要注意转移方程\(j-k\)的范围,枚举区间\([0,j]\)
程序
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll qpow(ll a,ll n)
{
ll r=1;
while(n)
{
if(n&1)
r=(r*a)%mod;
n>>=1;
a=(a*a)%mod;
}
return r;
}
ll C[360][360],dp[360][360];
int main()
{
int n;
scanf("%d",&n);
C[0][0]=C[1][0]=C[1][1]=1;
for(int i=2;i<=n;i++)
{
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=n;j++)
for(int k=0;k<=j;k++)
{
if(k!=i)
dp[i][j]=(dp[i][j]+dp[i-1][j-k]*C[j][k])%mod;
}
printf("%lld\n",(qpow(n,n)-dp[n][n]+mod)%mod);
return 0;
}