G题: Product
原题链接:https://ac.nowcoder.com/acm/contest/11255/G
题目大意
给定三个整数
n
,
k
,
D
(
1
≤
n
,
k
≤
50
,
0
≤
D
≤
1
0
8
)
n,k,D(1 \leq n,k \leq 50,0 \leq D \leq 10^8)
n,k,D(1≤n,k≤50,0≤D≤108) 。
定义非负整数序列
a
a
a 的权值为
D
!
∏
i
=
1
n
(
a
i
+
k
)
!
\frac{D!}{\prod\limits_{i=1}^{n}(a_i+k)!}
i=1∏n(ai+k)!D!
求所有满足以下条件的非负整数序列
a
a
a 的权值和:
- ∀ ∈ [ 1 , n ] , a i ≥ 0 \forall\in[1,n],a_i \ge 0 ∀∈[1,n],ai≥0 ;
- ∑ i = 1 n a i = D \sum_{i=1}^{n}a_i=D ∑i=1nai=D ;
答案对 998244353 998244353 998244353取模。
题解
(题解模块最后有附图)
直接考虑计算
∑
D
!
∏
i
=
1
n
(
a
i
+
k
)
!
(
∀
∈
[
1
,
n
]
,
a
i
≥
0
;
∑
i
=
1
n
a
i
=
D
)
\sum\frac{D!}{\prod\limits_{i=1}^{n}(a_i+k)!}(\forall\in[1,n],a_i \ge 0;\sum\limits_{i=1}^{n}a_i=D)
∑i=1∏n(ai+k)!D!(∀∈[1,n],ai≥0;i=1∑nai=D) 并不方便。
我们先舍去
a
i
+
k
a_i+k
ai+k 中的
+
k
+k
+k ,考虑①
∑
D
!
∏
i
=
1
n
a
i
!
(
∀
∈
[
1
,
n
]
,
a
i
≥
0
;
∑
i
=
1
n
a
i
=
D
)
\sum\frac{D!}{\prod\limits_{i=1}^{n}a_i!}(\forall\in[1,n],a_i \ge 0;\sum\limits_{i=1}^{n}a_i=D)
∑i=1∏nai!D!(∀∈[1,n],ai≥0;i=1∑nai=D) 类型的权值。
我们将该种类型的权值展开,写作
∑
D
!
a
1
!
a
2
!
.
.
.
a
n
!
\sum\frac{D!}{a_1!a_2!...a_n!}
∑a1!a2!...an!D! ,因为
∑
i
=
1
n
a
i
=
D
\sum\limits_{i=1}^{n}a_i=D
i=1∑nai=D ,所以该类型权值可以转化为组合数含义:共有
D
D
D 个不同的球,分为
n
n
n 组,求总方案数。显然,对于每个球有
n
n
n 个分组去向,所以总方案数应为
n
D
n^D
nD 。
然后我们考虑将
(
a
i
+
k
)
(a_i+k)
(ai+k) 中的
+
k
+k
+k 并入
a
i
a_i
ai 中,原式改写为②
∑
D
!
∏
i
=
1
n
a
i
!
(
∀
∈
[
1
,
n
]
,
a
i
≥
k
;
∑
i
=
1
n
a
i
=
D
+
n
k
)
\sum\frac{D!}{\prod\limits_{i=1}^{n}a_i!}(\forall\in[1,n],a_i \ge k;\sum\limits_{i=1}^{n}a_i=D+nk)
∑i=1∏nai!D!(∀∈[1,n],ai≥k;i=1∑nai=D+nk) (注意条件有改变,每一个
a
i
a_i
ai 加上
k
k
k 后应满足
a
i
≥
k
a_i\ge k
ai≥k ,同时总和加上
n
k
nk
nk 变为
D
+
n
k
D+nk
D+nk )。
直接考虑②较不方便,我们先尝试计算
a
i
≥
0
a_i\ge 0
ai≥0 条件的总方案数(合法方案数+非法方案数),即③
∑
D
!
∏
i
=
1
n
a
i
!
(
∀
∈
[
1
,
n
]
,
a
i
≥
0
;
∑
i
=
1
n
a
i
=
D
+
n
k
)
\sum\frac{D!}{\prod\limits_{i=1}^{n}a_i!}(\forall\in[1,n],a_i \ge 0;\sum\limits_{i=1}^{n}a_i=D+nk)
∑i=1∏nai!D!(∀∈[1,n],ai≥0;i=1∑nai=D+nk) 。
对于③,我们考虑将其转化为类似①的形式如下:
∑
(
D
+
n
k
)
!
a
1
!
a
2
!
.
.
.
a
n
(
∀
∈
[
1
,
n
]
,
a
i
≥
0
;
∑
i
=
1
n
a
i
=
D
+
n
k
)
\sum\frac{(D+nk)!}{a_1!a_2!...a_n}(\forall\in[1,n],a_i \ge 0;\sum\limits_{i=1}^{n}a_i=D+nk)
∑a1!a2!...an(D+nk)!(∀∈[1,n],ai≥0;i=1∑nai=D+nk) ,显然该式与③并不对等(分母处
D
!
−
>
(
D
+
n
k
)
!
D!->(D+nk)!
D!−>(D+nk)! ),我们在其前乘上修正系数
D
!
(
D
+
n
k
)
!
\frac{D!}{(D+nk)!}
(D+nk)!D! 使得与原式一致,即
D
!
(
D
+
n
k
)
!
∑
(
D
+
n
k
)
!
a
1
!
a
2
!
.
.
.
a
n
(
∀
∈
[
1
,
n
]
,
a
i
≥
0
;
∑
i
=
1
n
a
i
=
D
+
n
k
)
\frac{D!}{(D+nk)!}\sum\frac{(D+nk)!}{a_1!a_2!...a_n}(\forall\in[1,n],a_i \ge 0;\sum\limits_{i=1}^{n}a_i=D+nk)
(D+nk)!D!∑a1!a2!...an(D+nk)!(∀∈[1,n],ai≥0;i=1∑nai=D+nk) ,用类似①的方法化简得
D
!
(
D
+
n
k
)
!
n
D
+
n
k
\frac{D!}{(D+nk)!}n^{D+nk}
(D+nk)!D!nD+nk 。
然后我们考虑筛去那些存在
a
i
<
k
a_i<k
ai<k 的非法方案数,我们首先通过动态规划初始化,设
d
p
i
,
j
dp_{i,j}
dpi,j 表示组合数含义
j
j
j 个球分为
i
i
i 个非法组(即每组球数均少于
k
k
k ) ,那么我们通过枚举最后一个非法组的球数(同时需考虑球的分配情况有多种,即转移式中的组合项
C
C
C ),易得转移式:
d
p
i
,
j
=
∑
s
=
0
k
−
1
d
p
i
−
1
,
j
−
s
C
j
s
dp_{i,j}=\sum^{k-1}_{s=0}dp_{i-1,j-s}C^s_j
dpi,j=s=0∑k−1dpi−1,j−sCjs
那么我们可以通过容斥原理来筛掉这些解,结合③的变式,我们将②最终变形为解(记得考虑球的分布情况,乘上组合项
C
C
C ):
D
!
(
D
+
n
k
)
!
∑
i
=
0
n
(
−
1
)
i
∑
j
=
0
i
(
k
−
1
)
d
p
i
,
j
C
n
i
C
D
+
n
k
j
(
n
−
i
)
D
+
n
k
−
j
\frac{D!}{(D+nk)!}\sum^n_{i=0}(-1)^i\sum^{i(k-1)}_{j=0}dp_{i,j}C^i_nC^j_{D+nk}(n-i)^{D+nk-j}
(D+nk)!D!i=0∑n(−1)ij=0∑i(k−1)dpi,jCniCD+nkj(n−i)D+nk−j
下图为导图式推理,并带上更详细的注释(?):
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=55,mod=998244353;
ll n,k,D,C[N*N][N+1],dp[N][N*N],sum,facC,CC,ans;
ll powmod(ll x,ll p){ll ret=1;while(p){if(p&1)ret=ret*x%mod;x=x*x%mod;p>>=1;}return ret;}//快速幂
ll inv(ll x){return powmod(x,mod-2);}//求逆元
int main()
{
std::ios::sync_with_stdio(false),cin.tie(0);
ll i,j,l,o;
cin>>n>>k>>D;
o=N*N;
for(i=0;i<=o;i++)C[i][0]=1;
for(i=1;i<=o;i++){
l=min(N,i);
for(j=1;j<=l;j++){
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;//组合数打表
}
}
dp[0][0]=1;//dp初始化,0个球分0个非法组有1种方案
for(i=0;i<n;i++){
l=i*(k-1);
for(j=0;j<=l;j++){
for(o=0;o<k;o++)dp[i+1][j+o]=(dp[i+1][j+o]+dp[i][j]*C[j+o][o])%mod;//优化递推计算dp
}
}
for(i=0;i<=n;i++){
sum=0;
CC=(i&1?mod-C[n][i]:C[n][i]);
l=i*(k-1);
facC=1;
for(j=0;j<=l;j++){
sum=(sum+CC*powmod(n-i,D+n*k-j)%mod*dp[i][j]%mod*facC%mod)%mod;
facC=facC*(D+n*k-j)%mod*inv(j+1)%mod;//保存C(D+nk,j)项的值,该项的n过大不适用打表
}
ans=(ans+sum)%mod;
}
for(i=D+n*k;i>D;i--)ans=ans*inv(i)%mod;//乘上修正系数,省去了分子与分母的重合部分
cout<<ans<<endl;
return 0;
}