COCI 2019/2020 CONTEST #5 - Zapina
题意
给(n)个人分配(n)种不同的工作。
(n)个人拍成一列,第(i)个人只有在被严格安排到(i)份工作时会感到快乐(不会在意是什么工作,只在意数量)。
问至少有一人感到快乐的分配方案数量。
限制
(1leq nleq 350)
思路
考虑对立事件,事件“至少有一人快乐”的对立事件为“没有一个人感到快乐”
由于每份工作之间是相互独立的,所以安排方案总共有(n^n)种
所以所求可以转化成“总方案数”减去“没有一个人感到快乐”的方案数
考虑没有一个人感到快乐的情况,进行动态规划
令(dp[i][j])表示(i)个人分配(j)份工作,“没有一个人感到快乐”的方案数
单独考虑最后一个人被安排到的工作数量(k),容易得到转移方程
[dp[i][j]=dp[i][j]+C_{j}^{k}dp[i-1][j-k], k
eq i
]
表示(i)个人分配(j)份工作,可以由(i-1)个人分配(j-k)份工作转移而来,且第(i)个人被分配到的这(k)份工作可以用组合数(C_j^k)来得出方案数
注意这里的(k
eq 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
",(qpow(n,n)-dp[n][n]+mod)%mod);
return 0;
}