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
1≤m,n≤5⋅104
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
0≤ai≤105, ∑i=1nai≤105
思路
对于一种食物共有
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 级别。