班上有 N 名同学(学号 0∼N−1),有 M 种奖品。第 i 种奖品共有 ai 个。保证奖品总数 sum=∑i=0M−1ai 满足 N≤sum≤N+1,即恰好够每人分一个,或只多出一个。每位同学获得一个奖品,同种奖品之间不可区分,同学之间可区分(学号不同)。只要有一位同学获得的奖品种类不同,就算不同的方案。求方案数模 109+7。
其中 sum=∑ai(等于 N 或 N+1)。所以无论剩余 0 个还是 1 个奖品,答案都是 sum 个物品的全排列数除以各物品内部排列数的商。
组合意义解释
该公式有一个更直接的组合解释:我们暂时假设真的有 sum 个“位置”(同学),把 sum 个奖品全部分配给这 sum 个位置。方案数为 sum!/∏ai!。如果实际只有 N 个同学,且 sum=N+1,那么多出的那个奖品相当于“空气同学”,去掉它并不影响分配方案间的区别,因为每种剩余哪个奖品的情况在公式中已经按比例体现。因此公式统一成立。
1#include<cstdio>2#include<cstring>3#include<algorithm>4#include<iostream>5usingnamespace std;67constint N =1005;8constint mod =1e9+7;910int C[N +5][N +5], a[N +5];1112// 组合数预处理13voidinit(){14 C[0][0]=1;15for(int i =1; i <= N; i++){16 C[i][0]= C[i][i]=1;17for(int j =1; j < i; j++){18 C[i][j]=(C[i-1][j-1]+ C[i-1][j])% mod;19}20}21}2223intmain(){24init();2526int T;27scanf("%d",&T);2829while(T--){30int n, m, sum =0;31scanf("%d%d",&n,&m);3233for(int i =1; i <= m; i++){34scanf("%d",&a[i]);35 sum += a[i];36}3738int ans =1;39for(int i =1; i <= m; i++){40 ans =1LL* ans * C[sum][a[i]]% mod;41 sum -= a[i];42}4344printf("%d\n", ans);45}4647return0;48}