洛谷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;
}

好愚蠢的复杂办法啊,如果大家想看更简单的做法,戳这里(毕竟这题才黄题,扩展欧几里得的模版都绿了)

既然是质数,那似乎费马小定理也可以……

posted @ 2025-02-20 18:42  林晋堃  阅读(435)  评论(0)    收藏  举报