cf960G Bandit Blues 倍增NTT第一类斯大林数

题意是问有多少种长度为n的排列,满足前缀最大值是他本身的数量为A,后缀最大值是它本身的数量为B
如果你把每个前缀最大值和他后面的连续一段数放在一起,就相当于一个圆排列,每次断点都是这个最大值前面。
从最大值处把左右分开。
那么就相当于把长度 n − 1 n - 1 n1为排列分成 A + B − 1 A + B - 1 A+B1个圆排列的方案数。
还要乘上一个 ( A + B − 1 A − 1 ) \binom{A + B - 1}{A - 1} (A1A+B1)表示从中选 A − 1 A - 1 A1个圆排列放在最大值左边
那么问题就变成了预处理第一类斯大林数(我知道是特x

根据他的递推式
S n m = S n − 1 m − 1 + ( n − 1 ) S n − 1 m S_n^m = S_{n - 1}^{m - 1} + (n - 1) S_{n - 1}^{m} Snm=Sn1m1+(n1)Sn1m
可以得到 F n ( x ) = F n − 1 ( x ) x + ( n − 1 ) F n − 1 ( x ) F_n(x)=F_{n - 1}(x)x + (n - 1)F_{n - 1}(x) Fn(x)=Fn1(x)x+(n1)Fn1(x)
那么发现它的生成函数是
F n ( x ) = ∏ i = 0 n − 1 ( x + i ) F_n(x) = \prod_{i = 0}^{n - 1} (x + i) Fn(x)=i=0n1(x+i)
显然可以分治NTT
但是我知道我的常数大如狗
于是这东西还可以倍增x
这样复杂度从 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn)变成了 O ( n l o g n ) O(nlogn) O(nlogn)
我们可以考虑如何从前面一半推到后面一半
也就是从 ∏ i = 0 n − 1 ( x + i ) \prod_{i = 0}^{n - 1} (x + i) i=0n1(x+i)推到 ∏ i = n 2 n − 1 ( x + i ) \prod_{i = n}^{2n - 1} (x + i) i=n2n1(x+i)
考虑二项式定理展开,设 ∏ i = 0 n − 1 ( x + i ) = ∑ i = 0 n f i x i \prod_{i = 0}^{n - 1} (x + i) = \sum_{i = 0} ^ n f_i x^i i=0n1(x+i)=i=0nfixi
就变成了
∏ i = n 2 n − 1 ( x + i ) = ∏ i = 0 n − 1 ( x + n + i ) = ∑ i = 0 n f i ( x + n ) i   = ∑ i = 0 n f i ∑ j = 0 i ( i j ) x j n i − j \prod_{i = n}^{2n - 1} (x + i) = \prod_{i = 0}^{n - 1} (x + n + i) = \sum_{i = 0} ^ n f_i (x + n)^i \ = \sum_{i = 0} ^ n f_i \sum_{j = 0}^i \binom{i}{j} x ^ j n ^ {i - j} i=n2n1(x+i)=i=0n1(x+n+i)=i=0nfi(x+n)i =i=0nfij=0i(ji)xjnij
=   ∑ j = 0 i 1 j ! n j x j ∑ i = j n f i i ! n i ( i − j ) ! =   ∑ j = 0 i 1 j ! n j x j ∑ i = 0 n − j f n − i n n − i x i 1 ( n − i − j ) ! x j =\ \sum_{j = 0}^i \frac{1}{j! n ^ j} x ^ j \sum_{i = j} ^ n \frac{f_i i! n^i}{(i - j)!} = \ \sum_{j = 0}^i \frac{1}{j! n ^ j} x ^ j \sum_{i = 0} ^ {n - j} f_{n - i} n ^ {n - i} x ^ i \frac{1}{(n - i - j)!} x ^ j = j=0ij!nj1xji=jn(ij)!fii!ni= j=0ij!nj1xji=0njfninnixi(nij)!1xj
显然后面是一个卷积
然后我们就可以卷积出后面的东西,然后再和前面的东西卷一卷,就做完了
主要是常数大,听说极限可以卡到分治NTT时间的 15 % 15\% 15%

/* ***********************************************
Author        :BPM136
Created Time  :2019/8/15 9:42:49
File Name     :G.cpp
************************************************ */

#include <bits/stdc++.h>
#include <sys/timeb.h>
#define SZ(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
#define USE_CIN_COUT ios::sync_with_stdio(0)
#define filein(x) freopen(#x".in","r",stdin)
#define fileout(x) freopen(#x".out","w",stdout)
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout);
#define mkd(x) freopen(#x".in","w",stdout);

using namespace std;

int random(int l, int r) {
	static std::random_device rd;
	struct timeb timeSeed;
	ftime(&timeSeed);
	size_t seed = timeSeed.time * 1000 + timeSeed.millitm;  // milli time
	static std::mt19937 gen(seed);
	std::uniform_int_distribution<> u(l, r);
	return u(gen);
}

typedef long long ll;
typedef double db;
typedef long double ld;
typedef unsigned int ui;
typedef unsigned long long ull;
typedef pair<int, int> pii;


/eval多点求值,interpolation插值
//不可传入一个系数为负数的多项式,可能在减法的时候会爆mod
//常数稍微有点大
//G是TT的原根
//NTT前记得getRevRoot

int const TT = 998244353, G = 3;

inline int KSM (int a, int k) {
	int ret = 1 % TT;
	for (; k; k >>= 1, a = 1ll * a * a % TT)
		if (k & 1)
			ret = 1ll * ret * a % TT;
	return ret;
}

inline int add (int x, int y) {
    if (x + y >= TT)
        return x + y - TT;
    else
        return x + y;
}

inline int sub (int x, int y) {
    if (x - y >= 0)
        return x - y;
    else
        return x - y + TT;
}

namespace Polynom
{
	vector<int> rev, rt, e, one = {1};

	vector<int> operator + (vector<int> f, vector<int> g) {
		int n = (int)max(f.size(), g.size());
		f.resize(n);
		g.resize(n);
		for (int i = 0; i < n; ++i)
			f[i] = add(f[i], g[i]);
		return f;
	}

	vector<int> operator - (vector<int> f, vector<int> g) {
		int n = (int)max(f.size(), g.size());
		f.resize(n);
		g.resize(n);
		for (int i = 0; i < n; ++i)
			f[i] = sub(f[i], g[i]);
		return f;
	}

	void getRevRoot (int n) {
		int m = log(n) / log(2) + 1e-7;
		rev.resize(n);
		rt.resize(n);
		for (int i = 1; i < n; ++i)
			rev[i] = rev[i >> 1] >> 1 | (i & 1) << (m - 1);
		for (int len = 1, uni; len < n; len <<= 1) {
			uni = KSM(G, (TT ^ 1) / (len << 1));
			rt[len] = 1;
			for (int i = 1; i < len; ++i)
				rt[i + len] = 1ll * rt[i + len - 1] * uni % TT;
		}
	}

	void NTT (vector<int> &f, int n) {
		f.resize(n);
		for (int i = 0; i < n; ++i) 
			if (i < rev[i])
				swap(f[i], f[rev[i]]);
		for (int len = 1; len < n; len <<= 1)
			for (int i = 0; i < n; i += len << 1)
				for (int j = 0, x, y; j < len; j++) {
					x = f[i + j];
					y = 1ll * f[i + j + len] * rt[j + len] % TT;
					f[i + j] = add(x, y);
					f[i + j + len] = sub(x, y);
				}
	}

	vector<int> operator * (vector<int> f, vector<int> g) {
		int n = 1, m = (int)f.size() + (int)g.size() - 1, ivn;
		while (n < m)
			n <<= 1;
		ivn = KSM(n, TT - 2);
		getRevRoot(n);
		NTT(f, n);
		NTT(g, n);
		for (int i = 0; i < n; ++i)
			f[i] = 1ll * f[i] * g[i] % TT;
		reverse(f.begin() + 1, f.end());
		NTT(f, n);
		f.resize(m);
		for (int i = 0; i < m; ++i)
			f[i] = 1ll * f[i] * ivn % TT;
		return f;
	}

	vector<int> polyInv (vector<int> f, int m) {
		if (m == 1)
			return { KSM(f[0], TT - 2) };
		f.resize(m);
		vector<int> g = polyInv(f, m >> 1), h;
		g.resize(m);
		h.resize(m);
		for (int i = (m >> 1) - 1; ~i; --i)
			h[i] = (g[i] << 1) % TT;
		int n = m << 1, ivn = KSM(n, TT - 2);
		getRevRoot(n);
		NTT(f, n);
		NTT(g, n);
		for (int i = 0; i < n; ++i)
			f[i] = 1ll * f[i] * g[i] % TT * g[i] % TT;
		reverse(f.begin() + 1, f.end());
		NTT(f, n);
		for (int i = 0; i < m; ++i)
			h[i] = sub(h[i], 1ll * f[i] * ivn % TT);
		return h;
	}

	vector<int> operator ~ (vector<int> f) {
		int n = 1, m = (int)f.size();
		while (n < m)
			n <<= 1;
		f = polyInv(f, n);
		f.resize(m);
		return f;
	}

	vector<int> polyDeri (vector<int> f) {
		if (f.empty())
			return f;
		int n = (int)f.size();
		for (int i = 1; i < n; ++i)
			f[i - 1] = 1ll * f[i] * i % TT;
		f.resize(n - 1);
		return f;
	}

	vector<int> polyInte (vector<int> f) {
		int n = (int)f.size();
		f.resize(n + 1);
		for (int i = n; ~i; --i)
			f[i] = 1ll * f[i - 1] * KSM(i, TT - 2) % TT;
		return f;
	}

	vector<int> polyLn (vector<int> f) {
		int n = (int)f.size();
		f = polyInte((~f) * polyDeri(f));
		f.resize(n);
		return f;
	}

	vector<int> polyExp (vector<int> f, int n) {
		if (n == 1)
			return one;
		f.resize(n);
		vector<int> g = polyExp(f, n >> 1);
		g.resize(n);
		return g * (one - polyLn(g) + f);
	}

	inline vector<int> polyExp (vector<int> f) {
		int n = 1, m = (int)f.size();
		while (n < m)
			n <<= 1;
		f = polyExp(f, n);
		f.resize(m);
		return f;
	}

	inline vector<int> operator ^ (vector<int> f, int x) {
		int n = 0, m = (int)f.size();
		while (n < m - 1 && !f[n])
			n++;
		f.erase(f.begin(), f.begin() + n);
		e = {KSM(f.empty() ? 0 : f[0], x)};
		f = polyLn(f);
		f.resize(n = max(0ll, m - 1ll * n * x));
		for (int i = 1; i < n; ++i)
			f[i] = 1ll * f[i] * x % TT;
		f = polyExp(f);
		reverse(f.begin(), f.end());
		for (int i = m - n; i; --i)
			f.push_back(0);
		reverse(f.begin(), f.end());
		f.resize(m);
		return f;
	}

	inline vector<int> operator / (vector<int> f, vector<int> g) {
		int n = f.size(), m = g.size();
		reverse(f.begin(), f.end());
		reverse(g.begin(), g.end());
		f.resize(n - m + 1);
		g.resize(n - m + 1);
		vector<int> ret = f * (~g);
		ret.resize(n - m + 1);
		reverse(ret.begin(), ret.end());
		return ret;
	}

	inline vector<int> operator % (vector<int> f, vector<int> g) {
		vector<int> ret = f / g;
		ret = f - (g * ret);
		ret.resize(g.size() - 1);
		return ret;
	}
} // namespace Polynom

using namespace Polynom;

int const N = 300005;
int const mod = 998244353;

int fac[N], fac_inv[N];

vector<int> solve(int n) {
	if (n == 0)
		return { 1 };
	if (n == 1)
		return { 0, 1 };
	if (n & 1) {
		auto g = solve(n - 1);
		vector<int> t = { n - 1, 1 };
		return g * t;
	}
	auto f = solve(n >> 1);
	auto F = vector<int>(f.size(), 0);
	auto G = vector<int>(f.size(), 0);
	for (int i = 0; i < SZ(F); ++i) {
		F[i] = 1ll * f[i] * fac[i] % mod * KSM(n / 2, i) % mod;
		G[i] = fac_inv[i];
	}
	reverse(all(F));
	auto H = F * G;
	H.resize(F.size());
	reverse(all(H));
	for (int i = 0; i < SZ(H); ++i)
		H[i] = 1ll * fac_inv[i] * KSM(KSM(n / 2, i), mod - 2) % mod * H[i] % mod;
	return f * H;
}

int main() {
	USE_CIN_COUT;
	int n, a, b;
	cin >> n >> a >> b;
	if (a + b - 1 > n || a < 1 || b < 1) {
		cout << 0 << '\n';
		return 0;
	}
	fac[0] = fac[1] = fac_inv[0] = 1;
	for (int i = 2; i <= n; ++i)
		fac[i] = 1ll * fac[i - 1] * i % mod;
	fac_inv[n] = KSM(fac[n], mod - 2);
	for (int i = n - 1; i >= 1; --i)
		fac_inv[i] = 1ll * fac_inv[i + 1] * (i + 1) % mod;
	auto f = solve(n - 1);
	auto ans = 1ll * f[a + b - 2] * fac[a + b - 2] % mod * fac_inv[a - 1] % mod * fac_inv[b - 1] % mod;
	cout << ans << '\n';
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值