洛谷P5824 十二重计数法 题解

题意

\(n\) 个球,\(m\) 个盒子,求分别在以下 \(12\) 个限制条件下把球放入盒子里的方案数:

  • 球区分/不区分,盒子区分/不区分,无特殊限制/每个盒子至多放一个/每个盒子至少放一个。

答案对 \(998,244,353\) 取模。(\(1\le n,m\le 2\times 10^5\))

题解

好玩题,除了 \(\rm X\)\(\rm XII\)

球区分,盒子区分,无特殊限制

注意到每个球有 \(m\) 种盒子可以选,根据乘法原理即:

\[m^n \]

球区分,盒子区分,每个盒子至多放一个

首先注意到,如果 \(n>m\),则方案数显然为 \(0\)。排除掉这种情况后,考虑从 \(m\) 个盒子里选 \(n\) 个有球,再乘上 \(n\) 个球的相互区分的方案,即:

\[\dbinom{m}{n}n! =m^{\underline{n}} \]

球区分,盒子区分,每个盒子至少放一个

这个直接不太好做,注意到如果提前把每个盒子先放好,再放球,会重复计数。(考虑 \(1,2;3,4;5,6\),则提前放 \(1;3;5\) 再放剩下的和提前放 \(2;4;6\) 会被考虑为不同的情况)所以考虑容斥,容斥至少有多少个盒子是空的,即把一些钦定为空的,然后剩下的随便选,而随便选的就是无特殊限制的情况了,即:

\[\sum_{i=0}^m(-1)^i\dbinom{m}{i}(m-i)^n \]

球区分,盒子不区分,无特殊限制

注意到这个相当于把集合的元素划分为 \(m\) 个集合的方案数。发现很像贝尔数。考虑贝尔数关于斯特林数的公式:

\[B_n=\sum_{i=0}^n\begin{Bmatrix}n\\i\end{Bmatrix} \]

注意到组合意义其实就是枚举分成集合的数量。而这个因为盒子可以是空的,所以分成集合的数量可以是 \(0\sim \min(n,m)\)。换一换答案即为:

\[\sum_{i=0}^{\min(n,m)}\begin{Bmatrix}n\\i\end{Bmatrix} \]

但注意到,直接算是 \(\mathcal{O}(nm)\) 的,无法通过。观察斯特林数的通项公式:

\[\begin{Bmatrix}n\\m\end{Bmatrix}=\sum_{i=0}^m\dfrac{i^n(-1)^{m-i}}{i!(m-i)!} \]

发现是个卷积形式,用 \(\rm NTT\) 即可做到 \(\mathcal{O}(m\log m)\) 的时间复杂度内求解一行的斯特林数。然后统计答案即可。

球区分,盒子不区分,每个盒子至多放一个

相当于找 \(n\) 个盒子来放球,因为盒子不区分,所以答案为 \(1\)。而 \(n>m\) 的情况没有方案,所以答案为:

\[[n\le m] \]

球区分,盒子不区分,每个盒子至少放一个

类似无特殊限制的情况,这个要求每个至少放一个,这样分成集合的数量就只能是 \(m\) 了,所以答案为:

\[\begin{Bmatrix}n\\m\end{Bmatrix} \]

直接套通项公式算即可。

球不区分,盒子区分,无特殊限制

考虑插板法,但注意到可能有空的盒子,枚举哪些是空的即可:

\[\sum_{i=0}\dbinom{m}{i}\dbinom{n-1}{m-1-i} \]

注意到是范德蒙德卷积的形式,所以可以化为:

\[\dbinom{n+m-1}{m-1} \]

这个也有组合意义,考虑给每个盒子提前放上一个球,这样按照每个盒子至少放一个的方法做,最后把球拿走即为无特殊限制的情况。

球不区分,盒子区分,每个盒子至多放一个

类似球区分盒子区分的情况,只不过少乘个 \(n\) 个球的区分方案数,即:

\[\dbinom{m}{n} \]

球不区分,盒子区分,每个盒子至少放一个

插板法即可,方案数为:

\[\dbinom{n-1}{m-1} \]

球不区分,盒子不区分,无特殊限制

看起来直接从组合式子的角度很难入手。考虑设 \(f_{i,j}\) 表示把 \(i\) 个球放入 \(j\) 个盒子的方案数,考虑枚举最后一个盒子放不放东西即可:

\[f_{i,j}=f_{i-j,j}+f_{i,j-1} \]

注意到我们这样钦定了所有不放球的盒子都在最后,因为盒子不区分,所以这样是对的。

直接递推的时空复杂度是 \(\mathcal{O}(nm)\) 的,无法接受。考虑用 GF 表示这个 \(\rm dp\) 过程,注意到比较复杂度的变化是关于第一维的,所以考虑 GF 的下标放在第二维,即设:

\[F_m(z)=\sum_{p\ge 0}f_{p,m}z^p \]

这样,根据上面的式子,我们有:

\[F_m(z)=z^mF_m(z)+F_{m-1}(z) \]

简单代数变化:

\[F_m(z)=\dfrac{F_{m-1}(z)}{1-z^m} \]

注意到 \(F_0=1\),所以上式可以化简为:

\[F_m(z)=\prod_{i=1}^m\dfrac{1}{1-z^i} \]

现在就是处理这个 \(\prod\) 了。常见套路是取 \(\ln\)。(这个过程可以参考 付公主的背包

\[\prod_{i=1}^m\dfrac{1}{1-z^i}=\exp\sum_{i=1}^m-\ln(1-z^i) \]

然后是处理 \(\ln\),求导处理即可:

\[\begin{aligned}\ln(1-z^i)&=\int(\ln(1-z^i))'\\&=\int\dfrac{-iz^{i-1}}{1-z^i}\\&=-i\int\sum_{p\ge 0}z^{i(p+1)-1}\\&=-i\sum_{p\ge 1}\dfrac{z^{ip}}{ip}\\&=-\sum_{p\ge 1}\dfrac{z^{ip}}{p}\end{aligned} \]

然后带回即可:

\[\exp\sum_{i=1}^m\sum_{p\ge 1}\dfrac{z^{ip}}{p} \]

结束。枚举 \(i\),然后枚举倍数 \(ip\),求出来之后做 \(\exp\) 即可。上述过程都在 \(\bmod\ z^{n+1}\) 意义下做,即可做到在 \(\mathcal{O}(n\log^2 n)\) 时间复杂度内求解。(我用了半在线卷积求 \(\exp\)

答案即为:

\[[z^n]F_m(z) \]

球不区分,盒子不区分,每个盒子至多放一个

注意到这个情况和球区分盒子不区分的情况是等价的,所以答案为:

\[[n\le m] \]

球不区分,盒子不区分,每个盒子至少放一个

和无特殊限制的情况类似,我们提前在在所有盒子里放一个球即可,答案即为:

\[[z^{n-m}]F_m(z) \]

记得判 \(n< m\) 的情况。时间复杂度还是 \(\mathcal{O}(n\log^2n)\)

\(\tt code\)

#include <cstdio>
#include <cstring>
#include <algorithm>
const int mod = 998244353; typedef long long ll;
inline int ksm(int a, int b)
{
	int ret = 1;
	while (b)
	{
		if (b & 1) ret = (ll)ret * a % mod;
		a = (ll)a * a % mod; b >>= 1;
	}
	return ret;
}
const int N = 1e6 + 10; int fac[N], ifac[N];
inline void initF(int n)
{
	fac[0] = ifac[0] = 1;
	for (int i = 1; i <= n; ++i) fac[i] = (ll)fac[i - 1] * i % mod;
	ifac[n] = ksm(fac[n], mod - 2);
	for (int i = n - 1; i; --i) ifac[i] = (ll)ifac[i + 1] * (i + 1) % mod;
}
inline int C(int n, int m) { return m > n ? 0ll : (ll)fac[n] * ifac[m] % mod * ifac[n - m] % mod; }
inline int down(int n, int m) { if (m > n) return 0; int ret = 1; for (int i = n; i > n - m; --i) ret = (ll)ret * i % mod; return ret; }
int A[N], B[N], F[N], G[N], rev[N], lim, m;
inline void init(int n)
{
    lim = 1; m = 0; while (lim <= n) lim <<= 1, ++m;
    for (int i = 0; i < lim; ++i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (m - 1));
}
inline void NTT(int* f, int len, int on)
{
    for (int i = 0; i < len; ++i) if (i < rev[i]) std::swap(f[i], f[rev[i]]);
    for (int h = 2; h <= len; h <<= 1)
    {
        int gn = ksm(3, (ll)(mod - 1) / h * on % (mod - 1));
        for (int j = 0; j < len; j += h)
            for (int k = j, g = 1; k < j + h / 2; ++k, g = (ll)g * gn % mod)
            {
                int u = f[k], t = (ll)g * f[k + h / 2] % mod;
                f[k] = (u + t) % mod; f[k + h / 2] = ((u - t) % mod + mod) % mod;
            }
    }
    if (on == mod - 2) for (int i = 0, inv = ksm(len, on); i < len; ++i) f[i] = (ll)f[i] * inv % mod;
}
void cdq(int l, int r)
{
    if (l + 1 == r) return F[l] = l ? (ll)F[l] * ksm(l, mod - 2) % mod : 1, void();
    int mid = (l + r) >> 1; cdq(l, mid); init(r - l);
    for (int i = 0; i < mid - l; ++i) A[i] = F[i + l];
    for (int i = 0; i < r - l - 1; ++i) B[i] = G[i];
    for (int i = mid - l; i < lim; ++i) A[i] = 0;
    for (int i = r - l; i < lim; ++i) B[i] = 0;
    NTT(A, lim, 1); NTT(B, lim, 1);
    for (int i = 0; i < lim; ++i) A[i] = (ll)A[i] * B[i] % mod;
    NTT(A, lim, mod - 2);
    for (int i = mid - l - 1; i < r - l - 1; ++i) (F[i + l + 1] += A[i]) %= mod;
    cdq(mid, r);
}
inline void getExp(int n)
{
	for (int i = 1; i < n; ++i) G[i - 1] = (ll)i * G[i] % mod;
    G[n - 1] = 0; cdq(0, n);
}
int main()
{
	int n, m; scanf("%d%d", &n, &m); initF(N - 1);
	printf("%d\n%d\n", ksm(m, n), down(m, n));
	int ret = 0;
	for (int i = 0; i <= m; ++i)
	{
		int add = (ll)C(m, i) * ksm(m - i, n) % mod;
		if (i & 1) (ret += mod - add) %= mod; else (ret += add) %= mod;
	}
	printf("%d\n", ret); init(n + n);
	for (int i = 0; i <= n; ++i) 
        F[i] = (ll)ksm(i, n) * ifac[i] % mod, G[i] = ((i & 1) ? (mod - ifac[i]) : ifac[i]);
    NTT(F, lim, 1); NTT(G, lim, 1);
    for (int i = 0; i < lim; ++i) F[i] = (ll)F[i] * G[i] % mod;
    NTT(F, lim, mod - 2); ret = 0;
	for (int i = 0; i <= std::min(m, n); ++i) (ret += F[i]) %= mod;
	printf("%d\n%d\n", ret, (n <= m));
	ret = 0;
	for (int i = 0; i <= m; ++i)
	{
		int add = (ll)ksm(i, n) * ifac[i] % mod * ifac[m - i] % mod;
		if ((m - i) & 1) (ret += mod - add) %= mod; else (ret += add) %= mod;
	}
	printf("%d\n", ret); ret = 0;
	for (int i = 0; i < m; ++i) (ret += (ll)C(n - 1, m - 1 - i) * C(m, i) % mod) %= mod;
	printf("%d\n", ret); printf("%d\n", C(m, n));
	printf("%d\n", C(n - 1, m - 1));
	memset(F, 0, sizeof (F)); memset(G, 0, sizeof (G));
	for (int i = 1; i <= m; ++i) 
		for (int j = i; j <= n; j += i) (G[j] += ksm(j / i, mod - 2)) %= mod;
	getExp(n + 1); printf("%d\n", F[n]);
	printf("%d\n", (n <= m));
	if (n < m) return puts("0"), 0;
	memset(F, 0, sizeof (F)); memset(G, 0, sizeof (G));
	for (int i = 1; i <= m; ++i) 
		for (int j = i; j <= n - m; j += i) (G[j] += ksm(j / i, mod - 2)) %= mod;
	getExp(n - m + 1); printf("%d\n", F[n - m]);
	return 0;
}
posted @ 2022-04-07 19:57  zhiyangfan  阅读(93)  评论(0编辑  收藏  举报