MOCK NOIP+ 20221203 日志

作者:

在

,

还是张隽恺一如既往的恶趣味。

简略题解

A – 计数

每一次遇到排列问题,我都试着建出一张 $n$ 个点 $n$ 条边的图($x\rightarrow p_x$)后在上面思考。遗憾的是本题不能这样做。

记排列 $p$ 的逆为 $p^{-1}$,则有 $p=p^{-1}\lor (p\neq p^{-1}\land (p^{-1})^{-1}=p)$。

这是显然的:一个置换的逆元就是将其上下两行交换。故而我们可以计算出有多少个 $n$ 阶排列 $p=p^{-1}$,则 $n!$ 减去计数再除以二即为所求。

易得 $p=p^{-1}$ 的充要条件是 $p_{p_x}=x$,则可以视作每两个元素一对构成 $p_x=y,p_y=x$ 的形式,或者 $p_x=x$。记 $f(n)$ 表示 $p=p^{-1}$ 的 $n$ 阶排列数量,易得转移 $f(n)=f(n-1)+(n-1)f(n-2)$,边界有 $f(1)=1,f(2)=2$。时间复杂度 $\newcommand\bigO{\operatorname{O}}\bigO(n)$。

注意:本题条件下不一定存在 $2$ 的乘法逆元,故而记 $f'(n)$ 表示 $\frac{f(n)}{2}$ 来转移。

B – 点分治

可以发现这棵树就是树状数组展开后的样子。有 $x\ (x\in(0, 2^n))$ 的父亲为 $x-\operatorname{lowbit}(x)$。则两节点 $x,y$ 的 $\operatorname{lca}=x\ \operatorname{and}\ 2^{\log_2(x\oplus y)+1}$,即 $n$ 位二进制表示的最长公共前缀后补零。则统计固定距离的节点个数,可以钦定给定节点 $x$ 二进制表示的某一以 $1$ 结尾前缀作为最近公共祖先,后边选若干位置任填 $1$ 即可(应当减去非法情况)。

时间复杂度 $\bigO(n+\sum{k_i})$。

C – 博弈

记 $L=\sum_{i=1}^{n}l_i$。若区分每次选取的格子,可知任何一种填完恰好 $k$ 个格子后游戏结束的方案,出现的概率均为 $\frac{1}{L^{\underline{k}}}$。

我们枚举在哪一行结束了游戏,记其编号为 $i$。假若 $1\sim i-1$ 行填写了 $c_1$ 个格子(每一行均未填满),记有 $f(i-1,c_1)$ 种选择;$i+1\sim m$ 行填写了 $c_2$ 个格子,记有 $g(i+1,c_2)$ 种选择。则一共填写了 $s=c_1+c_2+l_i$ 格;又因为本题权值为拼接字符串表示的十进制数,且是“按照顺序依次填写”,则记 $h'(s,t)$ 表示按序填写 $s$ 个字符串,择取其中 $t$ 个以任意顺序摆放拼接,且第 $s$ 个字符串一定选中的权值总和。答案即
$$\sum_{i=1}^{n}\sum_{c_1=0}^{\sum_{j=1}^{i-1}l_j-1}\sum_{c_2=0}^{\sum_{j=i+1}^{n}l_j-1}\frac{f(i-1,c_1)g(i+1,c_2)(c_1+c_2)!h'(l_i+c_1+c_2,l_i)}{L^{\underline{l_i+c_1+c_2}}}$$

$f,g$ 的计算都是简单的背包,复杂度 $\bigO(n^3)$;思考如何快速计算 $h’$。

场上我试图维护“所有可能的结果字符串”的各项信息,同时直接计算暴力插入一个字串 $a_i$ 的贡献(插入后的数可以拆分成 $\text{原数更高位}\times 10^{|a_i|+|\text{原数更低位}|}+a_i\times 10^{|\text{原数更低位}|}+\text{原数更低位}$,维护每个串在所有位置劈开得到的“高位”和“低位”权值和),但发现式子首项无法用线性方式维护插入 $a_i$ 之后的贡献。

何钒佑教给我们一更聪明的贡献方法:将每一个字串的贡献独立,写作 $\sum a_i\times10^{|结果串更低位|}$。具体地,对于一个由 $t$ 个串拼接而成的的结果串,我们将其复制成 $t$ 份;对于第 $p$ 份,我们预留第 $p$ 位作为一个“窗口”,用第 $p$ 份串计算 $a_p$ 的贡献。若要将串 $a_p$ 填入,我们既可以填入窗口右侧,此时对权值积的贡献为 $10^{|a_p|}$;也可以占用窗口本身而计算 $a_p\times \sum \text{低位权值积}$;也可以填入窗口左侧,贡献不变。容易验证,它的和不重不漏地计算了每一个串的每一位代价;指数相加结果相乘,它具有结合律。于是记 $h(s,t,k,0/1)$ 表示考虑了 $a_1,\cdots,a_s$,择其中 $t$ 个拼接,且在窗口右侧已经填入 $k$ 个串,窗口是否被占用的权值总和;转移时做些处理,就能计算 $h'(s,t)$。

时间复杂度 $\bigO(n^3)$,常数大。

#include <bits/stdc++.h>
using namespace std;

#define inl inline
#define scd second
#define fst first
#define empb emplace_back
#define all(p) begin (p), end (p)
using ll = long long;
using ull = unsigned long long;
using pint = pair <int, int>;

constexpr int N = 305, L = 605, P = 1e9 + 9;
int n, m, l[N], pre[N], lsum; ll a[N], len[N]; char s[N];
ll f[N][L], g[N][L], fact[L]; int c[L][L];
ll ans, aru[N][N], pow10[L], fall[L], h[N][N][2], _h[N][N][2];

inl ll fpow (ll a, ll b) {
	ll res = 1; a %= P;
	for (; b; b >>= 1) {
		if (b & 1) (res *= a) %= P;
		(a *= a) %= P;
	}
	return res;
}
inl void calc_c (int lmt) {
	for (int x = 0; x <= lmt; ++x) {
		c[x][0] = 1;
		for (int y = 1; y <= x; ++y)
			c[x][y] = (c[x - 1][y] + c[x - 1][y - 1]) % P;
	}
	fact[0] = pow10[0] = fall[0] = 1;
	for (int x = 1, _x = lsum; x <= lmt; ++x, --_x)
		fact[x] = fact[x - 1] * x % P,
		fall[x] = fall[x - 1] * fpow (_x, P - 2) % P;
	for (int x = 1; x < N; ++x)
		pow10[x] = pow10[x - 1] * 10 % P;	
}
inl void calc_f () { // f (i, j): 前 i 行选 j 个,均未占满的方案数。
	f[0][0] = 1; int lsum = 0;
	for (int i = 0; i < m; ++i) {
		for (int j = 0; j <= lsum; ++j) {
			const ll _f = f[i][j];
			for (int k = 0; k < l[i + 1]; ++k)
				f[i + 1][k + j] += _f * c[l[i + 1]][k] % P;
		}
		lsum += l[i + 1];
		for (int j = 0; j <= lsum; ++j) f[i + 1][j] %= P;
	}
}
inl void calc_g () { // g (i, j): 后 i 行选 j 个,均未占满的方案数。
	g[m + 1][0] = 1; int lsum = 0;
	for (int i = m + 1; i > 1; --i) {
		for (int j = 0; j <= lsum; ++j) {
			const ll _g = g[i][j];
			for (int k = 0; k < l[i - 1]; ++k)
				g[i - 1][k + j] += _g * c[l[i - 1]][k] % P;
		}
		lsum += l[i - 1];
		for (int j = 0; j <= lsum; ++j) g[i - 1][j] %= P;
	}
}
inl void calc_h () { // h (i, j, k, b): a1, a2, ... ai 选了 j 个,窗口右侧选择 k 个,窗口未占/占用的方案数
	h[0][0][0] = 1;
	for (int i = 0; i < n; ++i) {
		memset (_h, 0, sizeof _h);
		const ll lco = pow10[len[i + 1]];
		for (int j = 0; j <= i; ++j) {
			for (const bool b : { 0, 1 })
				for (int k = 0; k <= j; ++k) {
					const ll val = h[j][k][b];
					_h[j + 1][k + 1][b] += val * (k + 1) % P * lco % P;
					_h[j + 1][k][b] += val * (j - k - b + 1);
				}
			for (int k = 0; k <= j; ++k)
				_h[j + 1][k][1] += h[j][k][0] * a[i + 1] % P;
		}
		for (int j = 0; j <= i + 1; ++j) {
			for (int k = 0; k <= j; ++k)
				(h[j][k][0] += _h[j][k][0]) %= P,
				(h[j][k][1] += _h[j][k][1]) %= P,
				aru[i + 1][j] += _h[j][k][1];
			aru[i + 1][j] %= P; // aru (i, j): 同题解中 h'(i, j)
		}
	}
}

int main () {
	/* MOCK NOIP+ 20221203 吴秋实 */
//	freopen ("game.in", "r", stdin);
//	freopen ("game.out", "w", stdout);

	scanf ("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i)
		scanf ("%d", l + i),
		pre[i] = pre[i - 1] + l[i];
	calc_c (lsum = pre[m]);
	for (int i = 1, j; i <= n; ++i) {
		scanf ("%s", s); j = 0;
		while (s[j])
			a[i] = (a[i] * 10ll + (s[j]^48)) % P,
			len[i] = ++j;
	}
	calc_f (); calc_g (); calc_h ();
	for (int i = 1; i <= m; ++i)
		for (int k1 = 0; k1 <= pre[i - 1]; ++k1)
			for (int k2 = 0, s = k1 + l[i]; k2 <= lsum - pre[i]; ++k2, ++s)
				ans += f[i - 1][k1] * g[i + 1][k2] % P * fact[k1 + k2] % P
					* aru[s][l[i]] % P * fall[s] % P;
	printf ("%lld", ans % P);

	return 0;
}

D – 最优化

最小费用增广流。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注