CCPC 2021威海 - G. Shinyruo and KFC(组合数,小技巧)

G. Shinyruo and KFC

题意
一共有 n n n 种食物,每种食物有 a i a_i ai 个。食物个数总和不超过 1 0 5 10^5 105
现在要把所有食物分给 k k k 个人,每个人可以拿多种食物,但每种食物最多拿 1 个。
对于人数 k k k 从 1 到 m m m,分别输出食物分配方案数。
答案模 998244353 998244353 998244353

1 ≤ m , n ≤ 5 ⋅ 1 0 4 1 \le m,n \le 5 \cdot 10^4 1m,n5104
0 ≤ a i ≤ 1 0 5 ,   ∑ i = 1 n a i ≤ 1 0 5 0\le a_i\le 10^5,\ \sum_{i=1}^n a_i\le 10^5 0ai105, i=1nai105

思路
对于一种食物共有 a i a_i ai 个,全部分给 k k k 个人,每人最多分一个,就相当于从 k 人中选出 ai 人,每人拿一个,那么分配方案为 C ( k , a i ) C(k, a_i) C(k,ai)

那么 n 种食物,分给 k 个人总的分配方案为: ∏ i = 1 n C ( k , a i ) \prod_{i=1}^{n} C(k, a_i) i=1nC(k,ai)
再遍历 m 个人,这样的话时间复杂度最低为 O(n * m),超时。

而题目中说 ai 总和不超过 1e5,那么 ai 的种类数就在 1 e 5 \sqrt {1e5} 1e5 级别,对于重复的直接用快速幂求幂次。
预处理逆元求组合数。

这样时间复杂度降为: O ( m n   l o g n ) O(m \sqrt n \ logn) O(mn  logn)

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
#define fi first
#define se second
#define endl '\n'
map<int,int> mp;

const int N = 200010, mod = 998244353;
int T, n, m;
int a[N];
struct node{
	int x, cnt;
}b[N];
int fac[N], inv[N], finv[N];

int qmi(int x, int y)
{
	int ans = 1;
	while(y)
	{
		if(y & 1) ans = ans * x % mod;
		x = x*x % mod;
		y >>= 1;
	}
	return ans;
}

void init(){
	fac[0]=inv[0]=inv[1]=finv[0]=finv[1]=1ll;
	for(int i=1;i<N;i++)fac[i]=fac[i-1]*i%mod;
	for(int i=2;i<N;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<N;i++)finv[i]=finv[i-1]*inv[i]%mod;
}

inline int C(int a,int b){
	if(b>a)return 0;
	else return fac[a]*finv[a-b]%mod*finv[b]%mod;
}

signed main(){
	Ios;
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> a[i], mp[a[i]]++;
	
	int cnt = 0;
	for(auto it : mp)
	{
		cnt++;
		b[cnt] = {it.first, it.second};
	}
	
	init();
	
	for(int i=1;i<=m;i++)
	{
		int ans = 1;
		for(int j=1;j<=cnt;j++)
		{
			ans = ans * qmi(C(i, b[j].x), b[j].cnt) % mod;
		}
		cout << ans << endl;
	}
	
	return 0;
}

经验

  • 如果 n 个数的总和不超过 m,那么这些数的种类个数在 m \sqrt m m 级别。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值