洛谷P10112 [GESP202312 八级] 奖品分配 题解
题目传送门。
看了题解才发现我有多蠢。
我的做法真是唐完了。
在此之前请学习扩展欧几里得定理和扩展欧几里得定理求逆元。
发现奖品要么 \(N\) 个,要么 \(N+1\) 个,于是分类讨论,当奖品只有 \(N\) 个的时候,显然所有奖品都要分给学生,那相当于有 \(N!\) 种分法,但是你会发现有重复部分,实际上比如奖品 \(2\) 号有两个,奖品 \(1\) 号只有一个,奖品 \(3\) 号有三个,那么其实只有 \(\frac{6!}{2! \times 3!}\) 种分法,所以分法数量就是 \(\frac{(\sum_{i = 1}^m a_i)!}{\prod_{i = 1}^m (a_i!)}\),由于答案会很大,需要取模,所以我们要求出每个 \(a_i!\) 在模 \(10^9+7\) 意义下的逆元,然后拿 \(N!\) 乘上每个 \(a_i\) 的逆元。然后第二种情况是有 \(N+1\) 个奖品,其实你会发现没啥区别,我们只需要枚举哪个奖品的其中一个不选(因为一个奖品可能会有很多个),其它的都选不就行了吗,当然,原本我们计算的是所有奖品都选的方案数,\(x\) 这个奖品的其中一个不选的话我们会发现我们求的答案多除了一个 \(a_x\),所以每次我们只需拿 \((N+1-1)!\) 乘 \(a_x\) 就行了(减 \(1\) 是因为我们应该选 \(N\) 个奖品,但是总共有 \(N+1\) 个奖品,前面为了方便理解才说求的是所有奖品都选的方案数,但是为了让代码更好写,还是减 \(1\) 比较好,当然你可以不减 \(1\),然后每次枚举的答案再除以 \(N+1\) 就行了)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e3+5;
const int mod = 1e9+7;
int x,y;
int a[N];
void exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x = 1;
y = 0;
return;
}
exgcd(b,a%b,y,x);
y-=a/b*x;
}
int jie[N];
int fac[N];
signed main()
{
jie[0] = 1;
for(int i = 1;i<=1001;i++)
{
jie[i] = jie[i-1]*i%mod;
}
for(int i = 0;i<=1001;i++)
{
exgcd(jie[i],mod,x,y);
x = (x%mod+mod)%mod;
fac[i] = x;
}
int _;
scanf("%lld",&_);
while(_--)
{
int n,m;
scanf("%lld %lld",&n,&m);
int sum = 0;
for(int i = 1;i<=m;i++)
{
scanf("%lld",&a[i]);
sum+=a[i];
}
if(sum == n+1)
{
int num = jie[sum-1],ans = 0;
for(int i = 1;i<=m;i++)
{
num = num*fac[a[i]]%mod;
}
for(int i = 1;i<=m;i++)
{
ans = (ans+num*a[i]%mod)%mod;
}
printf("%lld\n",ans);
}
else
{
int ans = jie[sum];
for(int i = 1;i<=m;i++)
{
ans = ans*fac[a[i]]%mod;
}
printf("%lld\n",ans);
}
}
return 0;
}
好愚蠢的复杂办法啊,如果大家想看更简单的做法,戳这里(毕竟这题才黄题,扩展欧几里得的模版都绿了)。
既然是质数,那似乎费马小定理也可以……

浙公网安备 33010602011771号