首先我们先上公式的:
首先说一下这个公式是怎么推出来的呢?(从大佬哪里问出来的)
首先我们思考一下排列组合问题,我们假设就是一共有 D 个球 一共有n种颜色每一种颜色有ai个球,问将这 个球的排列方式一共有多少种呢?
首先我们思考一下每个都确定的情况下,那么如果ai 都确定了,那么这种的排列组合是不是就是 , 然后把所有的 的和为 D 的情况加起来的总和 是不是就是 , 然后两边都除 我们就会得到这个公式了。
上面我们证明了这个公式,那么我们下面就要讨论这个题目和公式的关系了。
我们这个题目可以转换为:
这个公式。
如果没有 ai >= k的限制的话,我们的答案就是:
但是我们确实是有 ai >= k 的限制,那么我们就需要解决这个问题。
想要解决这个问题,我们需要提出容斥原理,什么是容斥原理呢?我们通过下面这个公式了解下。
看完这个公式是不是有所了解容斥原理了,如果不了解的话,大家可以自行百度,这里就不过多得解释了。那么接下来我们如何用容斥原理来解决这个问题呢。
我们通过上面的公式是不是可以了解到里面有很多的 Ai 我们就把 上面的 Ai 事件设置为( ai 这个数小于k )那么所有的不符合要求的总和就可以用上面的公式来表示了。
那么我们可以 用总和这个去减去不符合要求的总和然后得到正确答案
那么我们就需要举出上面所有的情况就可以了。对于上面容斥原理的公式,我们可以得出公式的每一部分(加减的每一部分)仅仅是确实了固定的几个ai<= k ,但是其他的 ai并没有要求。
假设。我们选了 x 个 ai 小于k ,那么这个式子的正负就是 (-1)^ x ,
然后从n个 ai 里面求出来 x 个数 ,那么有 C(n,x)(排列组合,从 n 个 选出来 x 个 的情况数)。
那么我们还有二个问题没有解
1.剩下的所有(n-x)个数套入这个题目公式是多少?
2.选出来这 x 数套入题目的公式结果是多少?
1.很容易发现剩下的 ai 没有要求,这里我们设定 我们选出来的这个 x 个数的和为 y 那么剩下的数套入公式可以通过这个公式来解决。
那么就是二问题了,我们选出来的这些数带进去是多少呢?
先上代码吧。
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
#include<cstdio>
#include<stack>
#include<set>
#include<cstdio>
#include<string>
#include<cstring>
#include<cstdlib>
#include<map>
#include<cmath>
#define ios std::ios::sync_with_stdio(0)
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const int N = 55;
const int M = N*N;
int n,k;
ll d;
ll dp[N][M]; // 选了 x 个 ai <= k 总和 为 y
ll c[M][M]; // 代表 C(n,i)
ll qpow(ll x,ll y)
{
ll ans = 1;
while(y)
{
if(y&1)
{
ans = (ans*x)%mod;
}
x = x*x%mod;
y >>= 1;
}
return ans;
}
void init()
{
c[0][0] = 1;
for(ll i = 1; i <= n*k ; i ++)
{
c[i][0] = 1;
for(ll j = 1 ; j <= i ; j++)
{
c[i][j] = (c[i-1][j]+c[i-1][j-1])%mod;
}
}
dp[0][0] = 1;
for(ll i = 1 ; i <= n ; i++)
{
for(ll j = 0 ; j <= i*(k-1) ; j++)
{
for(ll v = 0 ; v <= j && v < k ; v++)
{
dp[i][j] = (dp[i][j] + dp[i-1][j-v]*c[j][v]%mod)%mod;
}
}
}
}
signed main(){
cin >> n >> k >>d;
init();
ll ans = qpow(n,d+n*k); // 总情况(不带着分母)
for(ll i = 1; i <= n ; i++)
{
ll sgn = 0; // 正负号
ll mul = 1;
for(ll j = 0 ; j <= i*(k-1) ; j++)
{
if(i&1) sgn = mod-1;
else sgn = 1;
ans = (ans + sgn*c[n][i]%mod * qpow(n-i,d+n*k-j) %mod *dp[i][j] %mod *mul %mod)%mod;
//
mul = mul*(d+n*k-j)%mod*qpow(j+1,mod-2)%mod;// 通分需要 大家可以看到这里分母部分 还乘了 j+1(从 1 开始的)
}
}
for(int i = d+1 , D = d+n*k ; i <= D ; i++)
{
ans = ans*qpow(i,mod-2)%mod; // 乘分母 同分的分母是 (D+1)*(D+2)...(D+n*k)
}
printf("%lld\n",ans);
return 0;
}
这里面首先就是通分了,这里我们可以从主函数开始分析,我们是如何解决这个2问题的。
我们的2问题是求。选出来的ai 的带入公式的总和。
首先我们通分后,(代码中)分母是 每一项 还多了一个 j+1 的阶乘,那么也就是每一项多了一个选出来 ai和的阶乘。
那2问题应该是求这个东西。为什么要多出来这个阶乘呢?
然后再次回到 前面排列组合问题。式子的物理含义我们已经知道了,我们上面的 dp[i][j] 求得就是这个是式子的含义了。然后除以 D! 就是,这里的D!也就是这里为什么要 求 j+1 的原因了。