题解 I. Ice Drinking "蔚来杯"2022牛客暑期多校训练营3

传送门


【分析】

先推一波公式:

答案 \(res\) 显然有公式:(其中 \(D_n\) 表示 \(n\) 个元素全部错排的方案数)

\(\begin{aligned}res&={1\over n!}\sum_{x=0}^n\dbinom n x x^kD_{n-x}\\&=\sum_{x=0}^n{x^k\over x!}\cdot {D_{n-x}\over (n-x)!}\end{aligned}\)

由于错排问题有公式:\(\displaystyle D_n=\lfloor{n!\over e}\rfloor=n!\cdot \sum_{i=0}^n{(-1)^i\over i!}\)

因此答案为:\(\displaystyle res=\sum_{x=0}^n {x^k\over x!}\cdot \sum_{i=0}^{n-x} {(-1)^i\over i!}\)

\(x^k\) 用第二类斯特林数展开为下降幂,得到:

\(\begin{aligned}res&=\sum_{x=0}^n {x^k\over x!}\cdot \sum_{i=0}^{n-x} {(-1)^i\over i!}\\&=\sum_{t=0}^k\begin{Bmatrix}k\\t\end{Bmatrix}\sum_{x=0}^n{x^{\underline t}\over x!}\cdot \sum_{i=0}^{n-x}{(-1)^i\over i!}\\&=\sum_{t=0}^{\min(n, k)}\begin{Bmatrix}k\\t\end{Bmatrix}\sum_{x=0}^{n-t}{1\over x!}\cdot \sum_{i=0}^{(n-t)-x}{(-1)^i\over i!}\\&=\sum_{t=0}^{\min(n, k)}\begin{Bmatrix}k\\t\end{Bmatrix}\sum_{T=0}^{n-t}\sum_{i+j=T}{1\over j!}\cdot {(-1)^i\over i!}\\&=\sum_{t=0}^{\min(n, k)}\begin{Bmatrix}k\\t\end{Bmatrix}[1+\sum_{T=1}^{n-t}{1\over T!}\sum_{i+j=T}\dbinom T {i, j}(-1)^i]\\&=\sum_{t=0}^{\min(n, k)}\begin{Bmatrix}k\\t\end{Bmatrix}[1+\sum_{T=1}^{n-t}{1\over T!}\cdot (1-1)^T]\\&=\sum_{t=0}^{\min(n, k)}\begin{Bmatrix}k\\t\end{Bmatrix}\\&=Bell_k-\sum_{t=n+1}^k\begin{Bmatrix}k\\t\end{Bmatrix}\end{aligned}\)


现在的问题化为,如何求解出 \(Bell_k\)\(\begin{Bmatrix}k\\t\end{Bmatrix}\)

关于前者,根据题解提到的 Touchard 同余,可以得到贝尔数在模质数 \(p\) 意义下,有 \(Bell_n\equiv Bell_{n-p}+Bell_{n-(p-1)}\pmod {p}\)

这显然是一个 \(p\) 阶线性递推的形式,可以在 \(O(p^2\log k)\) 的时间内求解出 \(Bell_k\bmod p\) 的答案

而关于第二类斯特林数,有公式:

\(\displaystyle \begin{Bmatrix}x\\x-n\end{Bmatrix}=\sum_{k=0}^{n-1}\left<\left<\begin{matrix}n\\k\end{matrix}\right>\right>\dbinom {x+n-k-1} {2n}\)

其中,\(\displaystyle \left<\left<\begin{matrix}n\\k\end{matrix}\right>\right>\) 为第二类欧拉数,或二阶欧拉数。它表示多重集 \(\{1, 1, 2, 2, \cdots, n, n\}\) 构成的序列中,任意两个相同的 \(i\) 之间,所有数字都大于 \(i\) 的序列方案数。如 \(123321\) 是满足要求的,但 \(123123\) 不是,因为两个 \(2\) 之间含有小于 \(2\)\(1\) ;两个 \(3\) 之间含有小于 \(3\)\(1,2\)

第二类欧拉数有递推公式: \(\displaystyle \left<\left<\begin{matrix}n\\k\end{matrix}\right>\right>=(2n-k-1)\left<\left<\begin{matrix}n-1\\k-1\end{matrix}\right>\right>+(k+1)\left<\left<\begin{matrix}n-1\\k\end{matrix}\right>\right>\)

因此,模数为小质数 \(p\) 时,我们可以采用 \(O(p^2\log k)\) 的时间求解 \(Bell_k\bmod p\)\(O(p^2)\) 递推 \(\dbinom n m (0\leq n,m<p)\) 的数值,然后再在 \(O((k-n)^2)\) 的时间内地推出所有 \(O(k-n)\) 的第二类欧拉数,最后对于每个 \(\begin{Bmatrix}k\\t\end{Bmatrix}\) 利用第二类欧拉数和 Lucas 定理进行 \(O((k-t)\log k)\) 求解

总复杂度为 \(\displaystyle O(p^2\log k)+O(p^2)+O((k-n)^2)+\sum_{t=n+1}^k O((k-t)\log k)=O(p^2\log k+(k-n)^2\log k)\)

然而,我们的模数 \(862118861=857\times 997\times 1009\) 是个合数,不适用上面的算法。这当然很好解决,我们单独跑每个质数的结果,最后用中国剩余定理合并即可


【代码】

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
#define de(x) cout << #x <<" = "<<(x)<<endl
#define dd(x) cout << #x <<" = "<<(x)<<" "
#define all(a) a.begin(), a.end()
#define rep(i, a, b) for(int i=(a); i<(b); ++i)
#define per(i, a, b) for(int i=(b)-1; i>=(a); --i)
const int P=862118861, p[]={0, 857, 997, 1009};
ll n, k, S[1024][1024], Bell[1024];
inline ll kpow(ll a,ll x, ll p) { ll ans=1; for(;x;x>>=1, a=a*a%p) if(x&1) ans=ans*a%p; return ans; }
inline ll inv(ll a, ll p) { return kpow(a, p-2, p); }

int linear_recurrence(ll n, int m, vi a, vi c, int P) {
	if (n<m) return (a[n]+P)%P;
	vector<ll> v(m, 0), u(m<<1, 0);
	v[0] = 1;
	for(ll x = 0, W = n ? 1ll<<(63 - __builtin_clzll(n)) : 0; W; W >>= 1, x <<= 1) {
		fill(all(u), 0);
		int b = !!(n & W); if(b) x++;
		if(x < m) u[x] = 1;
		else {
			rep(i, 0, m) rep(j, 0, m) (u[i + b + j] += v[i] * v[j]) %= P;
			per(i, m, 2*m) rep(j, 0, m) (u[i - m + j] += c[j] * u[i]) %= P;
		}
		copy(u.begin(), u.begin() + m, v.begin());
	}
	ll ans = 0;
	rep(i, 0, m) (ans += v[i] * a[i]) %= P;
	return (ans+P)%P;
}

vi linear_a, linear_c;
inline int work_bell(ll k, int p) {
	linear_a.clear();
	linear_c.clear();
	S[0][0]=1;
	Bell[0]=1;
	linear_a.push_back(Bell[0]);
	for(int i=1; i<=p; ++i) {
		for(int j=1; j<=i; ++j)
			S[i][j]=(S[i-1][j-1]+(ll)S[i-1][j]*j)%p;
		Bell[i]=0;
		for(int j=1; j<=i; ++j)
			Bell[i]=(Bell[i]+S[i][j])%p;
		linear_a.push_back(Bell[i]);
	}
	linear_c.resize(p);
	linear_c[0]=linear_c[1]=1;
	return linear_recurrence(k, p, linear_a, linear_c, p);
}

int Euler2[5010][5010], C[1024][1024];
inline void pre_Euler2(int p) {
	Euler2[0][0]=1;
	for(int n=1; n<=5000; ++n) {
		Euler2[n][0]=1;
		for(int k=1; k<=n; ++k)
			Euler2[n][k]=((2ll*n-k-1)*Euler2[n-1][k-1]+(k+1ll)*Euler2[n-1][k])%p;
	}
}
inline void pre_C(int p) {
	C[0][0]=1;
	for(int i=1; i<p; ++i) {
		C[i][0]=C[i][i]=1;
		for(int j=1; j<i; ++j)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%p;
	}
}
inline int Lucas(ll n, ll m, int p) {
	int res=1;
	for(; n||m; n/=p, m/=p)
		res=(ll)res*C[n%p][m%p]%p;
	return res;
}
inline int S2(ll x, int n, int p) {
	int res=0;
	for(int k=0; k<=n; ++k)
		res=(res+(ll)Euler2[n][k]*Lucas(x+n-k-1, n+n, p))%p;
	return res;
}
inline int work(int p) {
	int res=work_bell(k, p);
	pre_Euler2(p);
	pre_C(p);
	for(ll t=n+1; t<=k; ++t)
		res=(res-S2(k, k-t, p))%p;
	return res<0?res+p:res;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>k;
	int x1=work(p[1]);
	int x2=work(p[2]);
	int x3=work(p[3]);
	ll m1=P/p[1], m2=P/p[2], m3=P/p[3];
	ll res=x1*m1*inv(m1, p[1])+x2*m2*inv(m2, p[2])+x3*m3*inv(m3, p[3]);
	cout<<res%P;
	cout.flush();
	return 0;
}
posted @ 2022-07-26 09:53  JustinRochester  阅读(47)  评论(0编辑  收藏  举报