JZOJ 6693. 2020.06.05省选模拟紫色彼岸樱推迟绽放 (自然数幂拆上升幂+组合意义矩阵乘法+NTT)

Posted coldchair

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JZOJ 6693. 2020.06.05省选模拟紫色彼岸樱推迟绽放 (自然数幂拆上升幂+组合意义矩阵乘法+NTT)相关的知识,希望对你有一定的参考价值。

Description:

幽幽子饿了,妖梦需要给幽幽子准备食物。
有 T 天,每天幽幽子划分成了 k 个时段,妖梦需要安排每一天的日程。
第 i 天妖梦准备了 D+i-1 道菜,每道菜有无数个。第 1 个时段是早餐,幽幽子会选择 L 道不同的菜吃。
接下来 k-1 个时段,每个时段可以选择 D+i-1 道菜中的一道吃或者选择 A 个活动中的一个参加,但是出于健康考虑,幽幽子不能连续两个时段同时吃菜。
k 和 A 是幽幽子事先决定好的,她给出了 Q 个询问,每次询问给出 L,D,T,问这 T 天每一天的安排的方案数之和。
由于答案可能很大,输出答案对 998244353 取模之后的结果。

https://gmoj.net/senior/#main/show/6693

前置知识:

自然数幂的两种拆法:

1.(n^m=sum_{i=0}^m S(m,i)*(inom{n}{i}*i!=n^{i↓}))
这个是有组合意义的,(n^m)意义为(m)个求放(n)个盒子里,那么枚举放了至少一个球的盒子数(i),分配即可。

2.(n^m=sum_{i=0}^m S(m,i)*n^{i↑}*(-1)^{m-i})
这个的意义不明,好像要用斯特林数的性质证明。

第二类斯特林数的容斥:
(S(n,m)=frac{1}{m!} sum_{i=0}^m C(m,i)*(-1)^{m-i}*i^n)

题解:

本题答案是((k--)后):
(sum_{n=0}^R inom{n}{L} sum_{i=0}^{k/2} A^{k-i}*inom{k-i}{i}*n^i)

尝试把(n^i)拆成上升幂以搞掉前面的(inom{n}{L})

(n^i)直接搞成上升幂(n^{i↑})不太好弄,因为提不出(n!),拆成((n+1)^{i↑})比较好。

(=sum_{i=0}^{k/2} A^{k-i}*inom{k-i}{i} sum_{n=0}^R inom{n}{L}*sum_{j=0}^{k/2+1}S(i+1,j+1)*(-1)^{i-j}*(n+1)^{j↑})

(=sum_{i=0}^{k/2} A^{k-i}*inom{k-i}{i} sum_{j=0}^{k/2}S(i+1,j+1)*(-1)^{i-j}*sum_{n=0}^R inom{n}{L}*(n+1)^{j↑})

(=sum_{i=0}^{k/2} A^{k-i}*inom{k-i}{i} sum_{j=0}^{k/2}S(i+1,j+1)*(-1)^{i-j}*sum_{n=0}^R frac{n!*(n+j)!}{L!*(n-L)!*n!})

(=sum_{i=0}^{k/2} A^{k-i}*inom{k-i}{i} sum_{j=0}^{k/2}S(i+1,j+1)*(-1)^{i-j}*sum_{n=0}^R frac{(n+j)!}{L!*(n-L)!})

(=sum_{i=0}^{k/2} A^{k-i}*inom{k-i}{i} sum_{j=0}^{k/2}S(i+1,j+1)*(-1)^{i-j}*sum_{n=0}^R frac{(n+j)!*(L+j)!}{(L+i)!*(n-L)!*L!})

(=sum_{i=0}^{k/2} A^{k-i}*inom{k-i}{i} sum_{j=0}^{k/2}S(i+1,j+1)*(-1)^{i-j}* inom{R+j+1}{L+j+1}frac{(L+j)!}{L!})

然后我们终于把R搞掉了,为了接下的方便,换换元:

(=sum_{i=0}^{k/2} inom{R+j+1}{L+j+1}frac{(L+j)!}{L!} sum_{m=0}^{k/2} S(m+1,i+1)*A^{k-m}*inom{k-m}{m})

干掉斯特林数:

(=sum_{i=0}^{k/2} inom{R+j+1}{L+j+1}frac{(L+j)!}{L!} sum_{m=0}^{k/2} A^{k-m}*inom{k-m}{m} frac{1}{(i+1)!}sum_{j=0}^{k/2+1} inom{i+1}{j}*(-1)^{i+1-j}*j^{m+1})

(=sum_{i=0}^{k/2} inom{R+j+1}{L+j+1}frac{(L+j)!}{L!} frac{1}{(i+1)!}sum_{j=0}^{k/2+1} inom{i+1}{j}*(-1)^{i+1-j}*sum_{m=0}^{k/2} A^{k-m}*inom{k-m}{m}*j^{m+1})

(m)那层循环,实际上可以对每个(j)快速算出,一种方法是多项式多点求值,另一种是发现它和一开始的式子长的很像,根据组合意义,可以写出一个矩阵乘法优化的dp。

然后发现再卷积一次就可以算出每个(i)后面的系数,那么每次枚举(i)即可(O(k))计算一次询问。

Code

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("
")
using namespace std;

const int mo = 998244353;

ll ksm(ll x, ll y) {
	ll s = 1;
	for(; y; y /= 2, x = x * x % mo)
		if(y & 1) s = s * x % mo;
	return s;
}

namespace ntt {
	const int nm = 1 << 18;
	ll w[nm], a[nm], b[nm];
	int r[nm];
	void build() {
		for(int i = 1; i < nm; i *= 2) {
			w[i] = 1; ll v = ksm(3, (mo - 1) / 2 / i);
			ff(j, 1, i) w[i + j] = w[i + j - 1] * v % mo;
		}
	}
	void dft(ll *a, int n, int f) {
		ff(i, 0, n) {
			r[i] = r[i / 2] / 2 + (i & 1) * (n / 2);
			if(i < r[i]) swap(a[i], a[r[i]]);
		} ll v;
		for(int i = 1; i < n; i *= 2) for(int j = 0; j < n; j += 2 * i) ff(k, 0, i) {
			v = a[i + j + k] * w[i + k], a[i + j + k] = (a[j + k] - v) % mo, a[j + k] = (a[j + k] + v) % mo;
		}
		if(f == -1) {
			reverse(a + 1, a + n);
			v = ksm(n, mo - 2);
			ff(i, 0, n) a[i] = (a[i] + mo) * v % mo;
		}
	}
}
using ntt :: dft;

const int M = 2e7 + 5;

ll fac[M], nf[M];

void build(int n) {
	fac[0] = 1; fo(i, 1, n) fac[i] = fac[i - 1] * i % mo;
	nf[n] = ksm(fac[n], mo - 2); fd(i, n, 1) nf[i - 1] = nf[i] * i % mo;
}

ll C(int n, int m) {
	if(n < m) return 0;
	return fac[n] * nf[m] % mo * nf[n - m] % mo;
}

int k, A, Q;
int L, D, T;

	struct P {
		ll a[2][2];
		P() { memset(a, 0, sizeof a);}
	};
	
	P operator * (P a, P b) {
		P c = P();
		fo(k, 0, 1) fo(i, 0, 1) fo(j, 0, 1)
			c.a[i][j] = (c.a[i][j] + a.a[i][k] * b.a[k][j]) % mo;
		return c;
	}
	
	P ksm(P x, int y) {
		P s = P(); s.a[0][0] = s.a[1][1] = 1;
		for(; y; y /= 2, x = x * x)
			if(y & 1) s = s * x;
		return s;
	}

const int N = 1 << 17;

int n;

ll f[N], g[N];

ll a[N], b[N];

void build() {
	fo(j, 0, k / 2 + 1) {
		P w = P();
		w.a[0][0] = A; w.a[1][0] = A;
		w.a[0][1] = -j;
		w = ksm(w, k);
		f[j] = (w.a[1][0] + w.a[1][1]) * j % mo;
	}
	int tp = 17;
	fo(i, 0, k / 2 + 1) a[i] = nf[i] * (i % 2 ? -1 : 1);
	fo(j, 0, k / 2 + 1) b[j] = f[j] * nf[j] % mo;
	dft(a, 1 << tp, 1); dft(b, 1 << tp, 1);
	ff(i, 0, 1 << tp) a[i] = a[i] * b[i] % mo;
	dft(a, 1 << tp, -1);
	fo(i, 0, k / 2) g[i] = a[i + 1];
}

ll solve(ll R, ll L) {
	ll ans = 0;
	fo(i, 0, k / 2)
		ans = (ans + C(i + R + 1, i + L + 1) % mo * fac[L + i] % mo * (i % 2 ? -1 : 1) * g[i]) % mo;
	ans = ans * nf[L] % mo;
	return ans;
}

int main() {
	ntt :: build();
	freopen("bloom.in", "r", stdin);
	freopen("bloom.out", "w", stdout);
	build(2e7);
	scanf("%d %d %d", &k, &A, &Q);
	k --;
	build();
	fo(ii, 1, Q) {
		scanf("%d %d %d", &L, &D, &T);
		ll ans = solve(D + T - 1, L) - solve(D - 1, L);
		ans = (ans % mo + mo) % mo;
		pp("%lld
", ans);
	}
}









以上是关于JZOJ 6693. 2020.06.05省选模拟紫色彼岸樱推迟绽放 (自然数幂拆上升幂+组合意义矩阵乘法+NTT)的主要内容,如果未能解决你的问题,请参考以下文章

6692. 2020.06.05省选模拟灵符「无寿之梦」

6692. 2020.06.05省选模拟灵符「无寿之梦」

6691. 2020.06.05省选模拟六道剑「一念无量劫」

6691. 2020.06.05省选模拟六道剑「一念无量劫」

省选模拟(66~70)

省选模拟1