2021ICPC济南区域赛题解CDJKL

本文详细介绍了2021年ICPC济南区域赛中涉及的算法问题,包括树的Euler序、组合计数、等差数列的性质和奇异级数。针对每个问题,提供了思路解析、关键代码片段和解题技巧,帮助读者理解如何在实际编程竞赛中优化策略,提高解题效率。

2021ICPC济南区域赛题解

题目链接:
https://pintia.cn/problem-sets/1459829212832296960

讲义、代码链接:
https://hytidel.lanzoub.com/b031c9dti
密码:cj1j

视频讲解链接:
https://www.bilibili.com/video/BV1Qe4y1D7c4



K. Search For Mafuyu

题意

有一棵包含编号为1∼n1\sim n1nnnn个节点的树.初始时你在111号节点,你想找的人M等概率地在2∼n2\sim n2n号节点.每一秒,你可走到与当前节点相邻的节点处.若你采取最优策略,求你找到M所需的时间的期望.

t  (1≤t≤1000)t\ \ (1\leq t\leq 1000)t  (1t1000)组测试数据.每组测试数据输入一个整数n  (2≤n≤100)n\ \ (2\leq n\leq 100)n  (2n100),表示节点数.接下来(n−1)(n-1)(n1)行每行输入两个数a,b  (1≤a,b≤n)a,b\ \ (1\leq a,b\leq n)a,b  (1a,bn),表示节点aaabbb间有边.数据保证输入的信息构成一棵树.

对每组测试数据,输出你采取最优策略时找到M所需的时间的期望,误差不超过1e−91\mathrm{e}-91e9.

思路

最优策略是走树的Euler序,即不走到底不回头.

[] 若不然,设(遍历时,即深度增加时)你从节点uuu走到节点vvv(非叶子节点)后又走回节点uuu.

(1)走到节点uuu时游戏未结束,说明M不在节点uuu处.走到节点vvv时游戏未结束,说明M不在节点vvv处.

​ 此时若从vvv返回uuu,则需多花1 s1\ \mathrm{s}1 s检查一个已经确认没有M的节点,显然这不是最优的.

(2)若M在节点vvv的子树中,显然继续遍历vvv的子树比先回到uuu再遍历uuu的子树更优.

(3)若M不在节点vvv的子树中(这需遍历完vvv的子树才能确定),又M不在已遍历过的节点中,则需从vvv回溯到uuu,继续遍历其他子树.

​ 显然这种遍历顺序是Euler序.

[] 由(2)(3)知:交换两子树的遍历顺序,所需的时间的期望不变.

对M在i  (2≤i≤n)i\ \ (2\leq i\leq n)i  (2in)节点的情况,只需在DFS过程中更新节点出入栈的时间即可得到Euler序,将Euler序累加到答案上,最后除以(n−1)(n-1)(n1)即可.

代码 -> 2021ICPC济南-K(Euler序+期望)

const int MAXN = 105;
int n;  // 节点数
vi edges[MAXN];
double ans = 0;
int tim;  // 时间

void dfs(int u, int fa) {
	tim++;  // u入栈
	for (auto v : edges[u]) {
		if (v == fa) continue;

		ans += tim;  // 将Euler序累加到答案上
		dfs(v, u);
	}
	tim++;  // u出栈
}

int main() {
	CaseT{
		cin >> n;
		ans = tim = 0;  // 清空
		for (int i = 1; i <= n; i++) edges[i].clear();

		for (int i = 0; i < n - 1; i++) {
			int u, v; cin >> u >> v;
			edges[u].push_back(v), edges[v].push_back(u);
		}

		dfs(1, -1);  // 从起点开始搜,起点无前驱节点
		cout << fixed << setprecision(18) << ans / (n - 1) << endl;
	}
}


C. Optimal Strategy

题意

有编号1∼n1\sim n1nn  (1≤n≤1e6)n\ \ (1\leq n\leq 1\mathrm{e}6)n  (1n1e6)个物品,其中第i  (1≤i≤n)i\ \ (1\leq i\leq n)i  (1in)个物品的价值为ai  (1≤ai≤n)a_i\ \ (1\leq a_i\leq n)ai  (1ain).A和B两人轮流操作,A先手.当前轮到的人在剩余的物品中取走一个物品.要求当所有物品取完时,两人各自手中的物品的价值之和都最大.两人都采取最优策略,求有多少种游戏进行序列(每轮玩家取的物品的编号构成的序列),答案对998244353998244353998244353取模.

思路

常见误区:最优策略是每次都拿剩下的物品中价值最大的物品.对样例111,若应拿价值最大的物品,则先手不应拿111,与样例解释不符.事实上,要使得游戏结束时两人各自手中的物品价值之和都最大,只需保证两人都拿到取得自己物品价值之和的最大值应拿的那些物品即可,与什么时候拿到那个物品无关.

显然游戏的结果只有先手必胜和平局两种情况,但这题不是博弈论,而是纯纯的组合计数.

具体地考察最优策略中两人分别应拿到哪些数.设cnt[i]cnt[i]cnt[i]表示价值为iii的数的个数.①cnt[i]cnt[i]cnt[i]为偶数时,显然A和B各拿一半;②cnt[i]cnt[i]cnt[i]为奇数时,A先手会多拿走一个,转化为B先手且cnt[i]cnt[i]cnt[i]为偶数的情况.综上,对cnt[i]cnt[i]cnt[i]个数iii,只需考虑其中⌊cnt[i]2⌋\left\lfloor\dfrac{cnt[i]}{2}\right\rfloor2cnt[i]个的分配情况.

注意到1≤ai≤1e61\leq a_i\leq1\mathrm{e}61ai1e6,不妨按顺序枚举每个数值的分配情况,而两人何时拿到自己应拿的物品不影响最终两人各自手中的物品的价值之和,故从小到大或从大到小枚举每个数值的分配情况都可以,下面以从小到大枚举为例.设当前准备分配数iii,则前面的1∼(i−1)1\sim (i-1)1(i1)sum=∑j=1i−1cnt[j]\displaystyle sum=\sum_{j=1}^{i-1}cnt[j]sum=j=1i1cnt[j]个数都已分配好,只需在前(sum+⌊cnt[i]2⌋)\left(sum+\left\lfloor\dfrac{cnt[i]}{2}\right\rfloor\right)(sum+2cnt[i])个数中选⌊cnt[i]2⌋\left\lfloor\dfrac{cnt[i]}{2}\right\rfloor2cnt[i]个给先手即可,方案数为Csum+⌊cnt[i]/2⌋⌊cnt[i]/2⌋C_{sum+\left\lfloor cnt[i]/2\right\rfloor}^{\left\lfloor cnt[i]/2\right\rfloor}Csum+cnt[i]/2cnt[i]/2.因以不同顺序拿到相同数值的数视为不同方案,故ans=∑i=1ncnt[i]!⋅Csum+⌊cnt[i]/2⌋⌊cnt[i]/2⌋\displaystyle ans=\sum_{i=1}^n cnt[i]!\cdot C_{sum+\left\lfloor cnt[i]/2\right\rfloor}^{\left\lfloor cnt[i]/2\right\rfloor}ans=i=1ncnt[i]!Csum+cnt[i]/2cnt[i]/2.直接计算时间复杂度为O(n2)O(n^2)O(n2),注意到sumsumsumcnt[j]cnt[j]cnt[j]的前缀和,只需在转移的同时维护即可,时间复杂度O(n)O(n)O(n).

代码 -> 2021ICPC济南-C(组合计数+推公式+前缀和)

const int MAXN = 1e6 + 5;
const int MOD = 998244353;
int n;
int cnt[MAXN];  // cnt[i]表示值为i的数的个数
int fac[MAXN], ifac[MAXN];

void init() {  // 预处理阶乘及其逆元
	fac[0] = 1;
	for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;

	ifac[MAXN - 1] = qpow(fac[MAXN - 1], MOD - 2, MOD);
	for (int i = MAXN - 1; i; i--) ifac[i - 1] = (ll)ifac[i] * i % MOD;
}

int C(int n, int m) {  // 组合数C(n,m)
	return (ll)fac[n] * ifac[m] % MOD * ifac[n - m] % MOD;
}

int main() {
	init();

	cin >> n;
	for (int i = 0; i < n; i++) {
		int a; cin >> a;
		cnt[a]++;
	}

	int ans = 1;  // 注意初始值为1而不是0
	int pre = 0;  // cnt[i]的前缀和
	for (int i = 1; i <= n; i++) {
		if (!cnt[i]) continue;

		ans = (ll)ans * fac[cnt[i]] % MOD * C(pre + cnt[i] / 2, cnt[i] / 2) % MOD;
		pre += cnt[i];
	}
	cout << ans;
}


D. Arithmetic Sequence

题意

给定一个长度为n  (1≤n≤2e5)n\ \ (1\leq n\leq 2\mathrm{e}5)n  (1n2e5)的数列a1,⋯ ,an  (0≤∣ai∣≤1e13)a_1,\cdots,a_n\ \ (0\leq |a_i|\leq 1\mathrm{e}13)a1,,an  (0ai1e13).现有两种操作:①选择数列中的一个数+1+1+1;②选择数列中的一个数−1-11.求将该数列变为等差数列的最小操作次数.

思路 by : keguaiguai

设最优解得到的数列为b1,⋯ ,bnb_1,\cdots,b_nb1,,bn,其公差为ddd,则bi=b1+(i−1)db_i=b_1+(i-1)dbi=b1+(i1)d,所需的操作次数为∑i=1n∣ai−bi∣=∑i=1n∣[ai−(i−1)d]−b1∣=ci=ai−(i−1)d∑i=1n∣ci−b1∣\displaystyle\sum_{i=1}^n |a_i-b_i|=\sum_{i=1}^n \left|[a_i-(i-1)d]-b_1\right|\xlongequal{c_i=a_i-(i-1)d}\sum_{i=1}^n |c_i-b_1|i=1naibi=i=1n[ai(i1)d]b1ci=ai(i1)di=1ncib1,易想到货舱选址问题.注意到此时未知cic_ici的值,而cic_ici的值可由aia_iaiddd确定,考虑先求出ddd.

设将序列变为以ddd为公差的等差数列所需的最小操作次数为f(d)f(d)f(d),它是一个下凸函数.

[] 注意到∣[ai−(i−1)(d+1)]−b1∣+∣[ai−(i−1)(d−1)]−b1∣\left|[a_i-(i-1)(d+1)]-b_1\right|+\left|[a_i-(i-1)(d-1)]-b_1\right|[ai(i1)(d+1)]b1+[ai(i1)(d1)]b1

=∣[ai−(i−1)d−b1]−(i−1)∣+∣[ai−(i−1)d−b1]+(i−1)∣≥2∣[ai−(i−1)d]−b1∣=|[a_i-(i-1)d-b_1]-(i-1)|+|[a_i-(i-1)d-b_1]+(i-1)|\geq 2|[a_i-(i-1)d]-b_1|=[ai(i1)db1](i1)+[ai(i1)db1]+(i1)2∣[ai(i1)d]b1,

两边求和得f(d+1)+f(d−1)≥2f(d)f(d+1)+f(d-1)\geq 2f(d)f(d+1)+f(d1)2f(d),故证.

注意到下凸函数是单谷的,可三分出其最小值点.注意每次三分都要求出序列的中位数,用sort可能会TLE,故改用nth_element函数在O(n)O(n)O(n)的时间复杂度内求出序列的中位数.

注意到nnn最大2e5,ai2\mathrm{e}5,a_i2e5,ai最大1e131\mathrm{e}131e13,则累加后可能会爆ll,故要开__int128.注意对__int128要重载abs函数.

代码 -> 2021ICPC济南-D(三分+nth_element)

#define ll __int128

const int MAXN = 2e5 + 5;
int n;
ll a[MAXN];  // 原数列
ll b[MAXN];  // 目标数列

ll abs(ll x) { return x < 0 ? -x : x; }  // 对__int128重载abs()

ll get_ans(ll d) {
	for (int i = 1; i <= n; i++) b[i] = a[i] - (i - 1) * d;

	ll res = 0;
	nth_element(b + 1, b + (n + 1) / 2, b + n + 1);  // 将中位数调整到下标(n+1)/2处
	ll mid = b[(n + 1) / 2];  // 中位数

	for (int i = 1; i <= n; i++) res += abs(a[i] - mid - (i - 1) * d);
	return res;
}

int main() {
	read(n);
	for (int i = 1; i <= n; i++) read(a[i]);

	ll l = -2e13, r = 2e13;
	while (l < r) {
		ll lmid = l + (r - l) / 3, rmid = r - (r - l) / 3;
		if (get_ans(lmid) < get_ans(rmid)) r = rmid - 1;
		else l = lmid + 1;
	}
	write(get_ans(l));
}


J. Determinant

题意

nnn阶矩阵AAA有性质:det⁡(ATA)=(det⁡A)2\det(A^TA)=(\det A)^2det(ATA)=(detA)2.给定nnn阶矩阵AAA∣det⁡A∣|\det A|detA,判断det⁡A\det AdetA的正负.

t  (1≤t≤100)t\ \ (1\leq t\leq 100)t  (1t100)组测试数据.每组测试数据第一行输入整数n  (1≤n≤100)n\ \ (1\leq n\leq 100)n  (1n100).第二行输入一个长度不超过1e41\mathrm{e}41e4的大数表示∣det⁡A∣|\det A|detA.接下来输入n×nn\times nn×n的矩阵AAA,其中元素的绝对值不超过1e91\mathrm{e}91e9.

对每组测试数据,若det⁡A>0\det A>0detA>0,输出’+‘;否则输出’-'.

思路 by : Paranoid5

[引理] 对$\forall 非零整数非零整数非零整数x_1及其相反数及其相反数及其相反数x_2,对奇模数,对奇模数,对奇模数p,有,有,x_1\not\equiv x_2\ \ (\mathrm{mod}\ p),且,且,x_1\ \mathrm{mod}\ p\neq 0,x_2\ \mathrm{mod}\ p\neq 0$.

[] x1+x2=0x_1+x_2=0x1+x2=0,则(x1+x2) mod p=0(x_1+x_2)\ \mathrm{mod}\ p=0(x1+x2) mod p=0,进而(x1 mod p+x2 mod p) mod p=0(x_1\ \mathrm{mod}\ p+x_2\ \mathrm{mod}\ p)\ \mathrm{mod}\ p=0(x1 mod p+x2 mod p) mod p=0.

ti=xi mod p  (i=1,2)t_i=x_i\ \mathrm{mod}\ p\ \ (i=1,2)ti=xi mod p  (i=1,2).若x1≡x2  (mod p)x_1\equiv x_2\ \ (\mathrm{mod}\ p)x1x2  (mod p),则t1=t2t_1=t_2t1=t2,且t1,t2<pt_1,t_2<pt1,t2<p.

​ 此时t1+t2t_1+t_2t1+t2为偶数,且0<t1+t2<2p0<t_1+t_2<2p0<t1+t2<2p.

(t1+t2) mod p=0(t_1+t_2)\ \mathrm{mod}\ p=0(t1+t2) mod p=0,则t1+t2=kp  (k∈Z)t_1+t_2=kp\ \ (k\in\mathbb{Z})t1+t2=kp  (kZ),结合0<t1+t2<2p0<t_1+t_2<2p0<t1+t2<2pk∈(0,2)k\in(0,2)k(0,2),即k=1k=1k=1.

​ 此时t1+t2=pt_1+t_2=pt1+t2=p,LHS\mathrm{LHS}LHS为偶数,RHS\mathrm{RHS}RHS为奇数,矛盾.

x1≢x2  (mod p)x_1\not\equiv x_2\ \ (\mathrm{mod}\ p)x1x2  (mod p).

∣det⁡A∣|\det A|detA对大素数MODMODMOD取模,用Gauss消元求det⁡A\det AdetA在模MODMODMOD意义下的值,比较两者是否相等即可.常见的模数可取1e9+71\mathrm{e}9+71e9+7.

代码 -> 2021ICPC济南-J(Gauss消元+大数取模)

const int MAXN = 605;
const int MOD = 1e9 + 7;
int n;
int a[MAXN][MAXN];

int gauss() {
	int sgn = 1;  // 行列式的值的符号
	for (int i = 1; i <= n; i++) {
		for (int j = i + 1; j <= n; j++) {
			while (a[i][i]) {
				int d = a[j][i] / a[i][i];
				for (int k = i; k <= n; k++)
					a[j][k] = ((ll)a[j][k] - (ll)d * a[i][k] % MOD + MOD) % MOD;
				swap(a[i], a[j]);
				sgn *= -1;
			}
			swap(a[i], a[j]);
			sgn *= -1;
		}
	}
	
	int res = 1;
	for (int i = 1; i <= n; i++) res = (ll)res * a[i][i] % MOD;
	return (res * sgn + MOD) % MOD;
}

int main() {
	CaseT{
		cin >> n;
		string s; cin >> s;
		int ans = 0;
		for (auto ch : s) ans = ((ll)ans * 10 % MOD + ch - '0') % MOD;

		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++) cin >> a[i][j];

		if (ans == gauss()) cout << '+' << endl;
		else cout << '-' << endl;
	}
}


L. Strange Series

题意 (2 s2\ \mathrm{s}2 s)

给定一个nnn次多项式P(x)=a0+a1x+⋯+anxnP(x)=a_0+a_1x+\cdots+a_nx^nP(x)=a0+a1x++anxn,其中ai≥0  (i=0,⋯ ,n)a_i\geq 0\ \ (i=0,\cdots,n)ai0  (i=0,,n).考察级数S=∑m=0∞P(m)m!\displaystyle S=\sum_{m=0}^\infty \dfrac{P(m)}{m!}S=m=0m!P(m).可以证明SSS是良好定义的,且S=peS=p\mathrm{e}S=pe,其中ppp是一个非负整数,e=∑m=0∞1m!≈2.71828\displaystyle \mathrm{e}=\sum_{m=0}^\infty \dfrac{1}{m!}\approx 2.71828e=m=0m!12.71828.求ppp.

t  (1≤t≤100)t\ \ (1\leq t\leq 100)t  (1t100)组测试数据.每组测试数据输入一个整数n  (0≤n≤1e5)n\ \ (0\leq n\leq 1\mathrm{e}5)n  (0n1e5).第二行输入(n+1)(n+1)(n+1)个整数a0,a1,⋯ ,an  (0≤ai<998244353)a_0,a_1,\cdots,a_n\ \ (0\leq a_i<998244353)a0,a1,,an  (0ai<998244353).数据保证至多有444组测试数据满足n>1000n>1000n>1000.

对每组测试数据,输出ppp998244353998244353998244353取模的结果.

思路

[引理] ∑m=0∞mnm!xm=ex⋅Q(x)\displaystyle\sum_{m=0}^\infty \dfrac{m^n}{m!}x^m=\mathrm{e}^x\cdot Q(x)m=0m!mnxm=exQ(x),其中Q(x)Q(x)Q(x)是关于xxxnnn次多项式.

[] 用数学归纳法证明.不妨设n−1n-1n1时结论成立.

∑m=0∞1m!⋅mnxm=x∑m=0∞1m!⋅mn−1⋅mxm−1=x∑m=0∞1m!⋅mn−1⋅ddx(xm)=x⋅ddx∑m=0∞1m!⋅mn−1xm\displaystyle \sum_{m=0}^\infty \dfrac{1}{m!}\cdot m^nx^m=x\sum_{m=0}^\infty \dfrac{1}{m!}\cdot m^{n-1}\cdot mx^{m-1}=x\sum_{m=0}^\infty \dfrac{1}{m!}\cdot m^{n-1}\cdot\dfrac{\mathrm{d}}{\mathrm{d}x}(x^m)=x\cdot \dfrac{\mathrm{d}}{\mathrm{d}x}\sum_{m=0}^\infty \dfrac{1}{m!}\cdot m^{n-1} x^mm=0m!1mnxm=xm=0m!1mn1mxm1=xm=0m!1mn1dxd(xm)=xdxdm=0m!1mn1xm

=x⋅ddx[exQ(x)]=x\cdot\dfrac{\mathrm{d}}{\mathrm{d}x}[\mathrm{e}^x Q(x)]=xdxd[exQ(x)],其中Q(x)Q(x)Q(x)是关于xxx(n−1)(n-1)(n1)次多项式.

注意到对exQ(x)\mathrm{e}^xQ(x)exQ(x)求导后多项式部分的最高次项仍为xn−1x^{n-1}xn1,乘上前面的xxx即证.

P(x)=a0+a1x+⋯+anxn  (ai≥0),S=∑m=0∞P(m)m!=peP(x)=a_0+a_1x+\cdots+a_nx^n\ \ (a_i\geq 0),\displaystyle S=\sum_{m=0}^\infty \dfrac{P(m)}{m!}=p\mathrm eP(x)=a0+a1x++anxn  (ai0),S=m=0m!P(m)=pe,则p=1eS=1elim⁡s→∞∑m=0sP(m)m!\displaystyle p=\dfrac{1}{\mathrm{e}}S=\dfrac{1}{\mathrm{e}}\lim_{s\rightarrow\infty}\sum_{m=0}^s \dfrac{P(m)}{m!}p=e1S=e1slimm=0sm!P(m).

P(m)m!=1m!(a0+a1m+⋯+anmn)=∑i=0naikik!\dfrac{P(m)}{m!}=\dfrac{1}{m!}(a_0+a_1m+\cdots+a_nm^n)=\displaystyle\sum_{i=0}^n a_i\dfrac{k^i}{k!}m!P(m)=m!1(a0+a1m++anmn)=i=0naik!ki,

​ 则p=1elim⁡s→∞∑m=0s∑i=0naimim!=∑i=0nai(1elim⁡s→∞∑m=0smim!)=∑i=0nai[1e∑m=0∞mim!]\displaystyle p=\dfrac{1}{\mathrm{e}}\lim_{s\rightarrow\infty}\sum_{m=0}^s \sum_{i=0}^n a_i\dfrac{m^i}{m!}=\sum_{i=0}^n a_i\left(\dfrac{1}{\mathrm{e}}\lim_{s\rightarrow\infty}\sum_{m=0}^s \dfrac{m^i}{m!}\right)=\sum_{i=0}^n a_i\left[\dfrac{1}{\mathrm{e}}\sum_{m=0}^\infty \dfrac{m^i}{m!}\right]p=e1slimm=0si=0naim!mi=i=0nai(e1slimm=0sm!mi)=i=0nai[e1m=0m!mi].

由引理知:∑m=0∞mim!xm=ex⋅Q(x)\displaystyle \sum_{m=0}^\infty \dfrac{m^i}{m!}x^m=\mathrm{e}^x\cdot Q(x)m=0m!mixm=exQ(x),其中Q(x)Q(x)Q(x)是关于xxxiii次多项式,进而p=∑i=0nai⋅Q(1)\displaystyle p=\sum_{i=0}^n a_i \cdot Q(1)p=i=0naiQ(1),同时也证明了p∈Np\in\mathbb{N}pN.

下面讨论如何求Q(x)Q(x)Q(x)的系数.

i=0i=0i=0时,∑m=0∞1m!xm=ex⋅1\displaystyle \sum_{m=0}^\infty \dfrac{1}{m!}x^m=\mathrm{e}^x\cdot 1m=0m!1xm=ex1,此时Q(x)=1,Q(1)=1Q(x)=1,Q(1)=1Q(x)=1,Q(1)=1.

i=1i=1i=1时,∑m=1∞mm!xm=ex⋅x\displaystyle \sum_{m=1}^\infty \dfrac{m}{m!}x^m=\mathrm{e}^x\cdot xm=1m!mxm=exx,此时Q(x)=x,Q(1)=1Q(x)=x,Q(1)=1Q(x)=x,Q(1)=1.

i=2i=2i=2时,∑m=1∞m2m!xm=ex⋅x(x+1)\displaystyle \sum_{m=1}^\infty \dfrac{m^2}{m!}x^m=\mathrm{e}^x\cdot x(x+1)m=1m!m2xm=exx(x+1),此时Q(x)=x(x+1),Q(1)=2Q(x)=x(x+1),Q(1)=2Q(x)=x(x+1),Q(1)=2.

i=3i=3i=3时,∑m=1∞m3m!xm=ex⋅x(x2+3x+1)\displaystyle \sum_{m=1}^\infty \dfrac{m^3}{m!}x^m=\mathrm{e}^x\cdot x(x^2+3x+1)m=1m!m3xm=exx(x2+3x+1),此时Q(x)=x(x2+3x+1),Q(1)=5Q(x)=x(x^2+3x+1),Q(1)=5Q(x)=x(x2+3x+1),Q(1)=5.

i=4i=4i=4时,∑m=1∞m4m!xm=ex⋅x(x3+6x2+7x+1)\displaystyle \sum_{m=1}^\infty \dfrac{m^4}{m!}x^m=\mathrm{e}^x\cdot x(x^3+6x^2+7x+1)m=1m!m4xm=exx(x3+6x2+7x+1),此时Q(x)=x(x3+6x2+7x+1),Q(1)=15Q(x)=x(x^3+6x^2+7x+1),Q(1)=15Q(x)=x(x3+6x2+7x+1),Q(1)=15.

i=5i=5i=5时,∑m=1∞m5m!xm=ex⋅x(x4+10x3+25x2+15x+1)\displaystyle \sum_{m=1}^\infty \dfrac{m^5}{m!}x^m=\mathrm{e}^x\cdot x(x^4+10x^3+25x^2+15x+1)m=1m!m5xm=exx(x4+10x3+25x2+15x+1),

​ 此时Q(x)=x(x4+10x3+25x2+15x+1),Q(1)=52Q(x)=x(x^4+10x^3+25x^2+15x+1),Q(1)=52Q(x)=x(x4+10x3+25x2+15x+1),Q(1)=52.

易联想到Bell数:B0=1,B1=1,B2=2,B3=5,B4=15,B5=52,⋯B_0=1,B_1=1,B_2=2,B_3=5,B_4=15,B_5=52,\cdotsB0=1,B1=1,B2=2,B3=5,B4=15,B5=52,,它可用递推求得.

注意到nnn最大为1e51\mathrm{e}51e5,暴力递推出Bell数会TLE.考虑用Bell数的生成函数求Bell数.

Bell数的定义:将nnn个不同的元素的集合划分为任意多个非空子集的方案数.

nnn个元素的方案数为fnf_nfn.固定最后一个元素xxx,枚举最后一个元素所在的集合的元素个数i∈[1,n]i\in[1,n]i[1,n],从剩下的(n−1)(n-1)(n1)个元素中选(i−1)(i-1)(i1)个元素与xxx在同一集合中,其他(n−i)(n-i)(ni)个元素任意划分,故fn=∑i=1nCn−1i−1⋅fn−i\displaystyle f_n=\sum_{i=1}^n C_{n-1}^{i-1}\cdot f_{n-i}fn=i=1nCn1i1fni,调整下标得fn+1=∑i=0nCni⋅fn−i\displaystyle f_{n+1}=\sum_{i=0}^n C_n^i\cdot f_{n-i}fn+1=i=0nCnifni.

fff的EGF为F(x)F(x)F(x),则fn+1(n+1)!=∑i=0nfn−i(n−i)!⋅1i!(n+1)\displaystyle \dfrac{f_{n+1}}{(n+1)!}=\sum_{i=0}^n \dfrac{f_{n-i}}{(n-i)!}\cdot\dfrac{1}{i!(n+1)}(n+1)!fn+1=i=0n(ni)!fnii!(n+1)1,进而(n+1)⋅fn+1(n+1)!=∑i=0nfn−i(n−i)!⋅1i!\displaystyle (n+1)\cdot\dfrac{f_{n+1}}{(n+1)!}=\sum_{i=0}^n \dfrac{f_{n-i}}{(n-i)!}\cdot\dfrac{1}{i!}(n+1)(n+1)!fn+1=i=0n(ni)!fnii!1,

​ 即[xn]F’(x)=∑i=0n[xn−i]F(x)⋅[xi]ex\displaystyle [x^n]F’(x)=\sum_{i=0}^n [x^{n-i}]F(x)\cdot [x^i]\mathrm{e}^x[xn]F(x)=i=0n[xni]F(x)[xi]ex,亦即F′(x)=F(x)exF'(x)=F(x)\mathrm{e}^xF(x)=F(x)ex.

y=F(x)y=F(x)y=F(x),则dydx=exy\dfrac{\mathrm{d}y}{\mathrm{d}x}=\mathrm{e}^xydxdy=exy,移项得dyy=exdx\dfrac{\mathrm{d}y}{y}=\mathrm{e}^x\mathrm{d}xydy=exdx,两边积分得ln⁡y=ex+C\ln y=\mathrm{e}^x+Clny=ex+C,故y=exp⁡(ex+C)y=\exp(\mathrm{e}^x+C)y=exp(ex+C).

n=1n=1n=1时只有一种划分方案,代入得C=−1C=-1C=1,故F(x)=exp⁡(ex−1)F(x)=\exp(\mathrm{e}^x-1)F(x)=exp(ex1).多项式exp求即可,时间复杂度O(nlog⁡n)O(n\log n)O(nlogn).

代码 -> 2021ICPC济南-L(Bell数+生成函数)

const int MAXN = 6e5 + 5;
const int MOD = 998244353;

int rev[MAXN];  // rev[x]表示二进制数x镜像后的结果

void NTT(int* y, int lim, int op) {  // op=1时为NTT,op=-1时为INTT
	for (int i = 0; i < lim; i++)  // 做一遍BRP,将系数位置调整为递归后的位置
		if (rev[i] < i) swap(y[i], y[rev[i]]);

	for (int l = 2; l <= lim; l <<= 1) {  // 模拟合并过程,l为当前区间长度
		int g = qpow(3, (MOD - 1) / l, MOD);
		for (int i = 0; i < lim; i += l) {
			int cur = 1;
			int k = l >> 1;
			for (int j = 0; j < k; j++, cur = (ll)cur * g % MOD) {
				int tmp = (ll)y[i + j + k] * cur % MOD;
				y[i + j + k] = (y[i + j] - tmp + MOD) % MOD;  // 奇次项
				y[i + j] = ((ll)y[i + j] + tmp) % MOD;  // 偶次项
			}
		}
	}

	if (op == -1) {  // INTT
		reverse(y + 1, y + lim);
		int inv = qpow(lim, MOD - 2, MOD);
		for (int i = 0; i < lim; i++) y[i] = (ll)y[i] * inv % MOD;
	}
}

void PolyMul(int* a, int* b, int n, int m) {  // n次多项式a乘m次多项式b,结果放在a中
	int lim = 1, len = 0;
	while (lim < (n + m << 1)) lim <<= 1, len++;
	for (int i = 1; i < lim; i++) rev[i] = (rev[i >> 1] >> 1 | ((i & 1) << (len - 1)));

	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, -1);
}

int n;
int f[MAXN], derf[MAXN], invf[MAXN];  // f、f的导数、f的逆
int bell[MAXN];

int tmp[MAXN];  // 递归过程中临时存放系数的数组

void PolyInv(int deg, int* a, int* res) {
	if (deg == 1) {
		res[0] = qpow(a[0], MOD - 2, MOD);
		return;
	}

	PolyInv((deg + 1) >> 1, a, res);  // 递归求解

	int lim = 1, len = 0;
	while (lim < (deg << 1)) lim <<= 1, len++;

	for (int i = 1; i < lim; i++) rev[i] = (rev[i >> 1] >> 1 | ((i & 1) << (len - 1)));

	for (int i = 0; i < lim; i++) tmp[i] = a[i] * (i < deg);  // i≥deg的项在模意义下为0

	NTT(tmp, lim, 1), NTT(res, lim, 1);
	for (int i = 0; i < lim; i++)
		res[i] = (ll)(2 - (ll)tmp[i] * res[i] % MOD + MOD) % MOD * res[i] % MOD;
	NTT(res, lim, -1);

	for (int i = deg; i < lim; i++) res[i] = 0;  // i≥deg的项在模意义下为0
}

void PolyDer(int deg, int* a, int* res) {  // 对deg次多项式a求导,结果放在res中
	for (int i = 1; i < deg; i++) res[i - 1] = (ll)i * a[i] % MOD;
	res[deg - 1] = 0;
}

void PolyInt(int deg, int* a, int* res) {  // 对deg次多项式a求不定积分(C=0),结果放在res中
	res[0] = 0;
	for (int i = 1; i < deg; i++) res[i] = (ll)a[i - 1] * qpow(i, MOD - 2, MOD) % MOD;
}

void PolyLn(int n, int* a, int* res) {  // 对n次多项式a求ln,结果放在res中
	memset(derf, 0, so(derf));
	memset(invf, 0, so(invf));

	int deg = 1;
	while (deg <= n) deg <<= 1;

	PolyDer(deg, a, derf);  // a的导数
	PolyInv(deg, a, invf);  // a的逆
	PolyMul(derf, invf, deg, deg);
	PolyInt(deg, derf, res);
}

int lnres[MAXN];  // res的ln
int tmpa[MAXN];  // 递归过程中临时存放系数的数组

void PolyExp(int deg, int* a, int* res) {  // 对deg次多项式a求exp,结果放在res中
	if (deg == 1) {
		res[0] = 1;
		return;
	}

	PolyExp(deg + 1 >> 1, a, res);

	PolyLn(deg, res, lnres);

	int lim = 1, len = 0;
	while (lim < (deg << 1)) lim <<= 1, len++;
	for (int i = 1; i < lim; i++) rev[i] = (rev[i >> 1] >> 1 | ((i & 1) << (len - 1)));

	for (int i = 0; i < deg; i++) tmpa[i] = a[i];
	for (int i = deg; i < lim; i++) tmpa[i] = lnres[i] = 0;
	for (int i = 0; i < lim; i++) tmpa[i] = (((ll)tmpa[i] - lnres[i]) % MOD + MOD) % MOD;
	tmpa[0]++;

	NTT(tmpa, lim, 1), NTT(res, lim, 1);
	for (int i = 0; i < lim; i++) res[i] = (ll)res[i] * tmpa[i] % MOD;
	NTT(res, lim, -1);
	for (int i = deg; i < lim; i++) res[i] = 0;
}

int fac[MAXN], ifac[MAXN];

void init() {  // 预处理Bell数
	fac[0] = 1;
	for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;

	ifac[MAXN - 1] = qpow(fac[MAXN - 1], MOD - 2, MOD);
	for (int i = MAXN - 1; i; i--) ifac[i - 1] = (ll)ifac[i] * i % MOD;

	n = 1e5 + 1;
	for (int i = 1; i < n; i++) f[i] = ifac[i];

	PolyExp(n, f, bell);

	for (int i = 0; i < n; i++) bell[i] = (ll)bell[i] * fac[i] % MOD;
}

int main() {
	init();  // 预处理Bell数
	
	CaseT{
		int n; cin >> n;
		int ans = 0;
		for (int i = 0; i <= n; i++) {
			int a; cin >> a;
			ans = ((ll)ans + (ll)a * bell[i]) % MOD;
		}
		cout << ans << endl;
	}
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值