还是张隽恺一如既往的恶趣味。
简略题解
A – 计数
每一次遇到排列问题,我都试着建出一张 个点 条边的图()后在上面思考。遗憾的是本题不能这样做。
记排列 的逆为 ,则有 。
这是显然的:一个置换的逆元就是将其上下两行交换。故而我们可以计算出有多少个 阶排列 ,则 减去计数再除以二即为所求。
易得 的充要条件是 ,则可以视作每两个元素一对构成 的形式,或者 。记 表示 的 阶排列数量,易得转移 ,边界有 。时间复杂度 。
注意:本题条件下不一定存在 的乘法逆元,故而记 表示 来转移。
B – 点分治
可以发现这棵树就是树状数组展开后的样子。有 的父亲为 。则两节点 的 ,即 位二进制表示的最长公共前缀后补零。则统计固定距离的节点个数,可以钦定给定节点 二进制表示的某一以 结尾前缀作为最近公共祖先,后边选若干位置任填 即可(应当减去非法情况)。
时间复杂度 。
C – 博弈
记 。若区分每次选取的格子,可知任何一种填完恰好 个格子后游戏结束的方案,出现的概率均为 。
我们枚举在哪一行结束了游戏,记其编号为 。假若 行填写了 个格子(每一行均未填满),记有 种选择; 行填写了 个格子,记有 种选择。则一共填写了 格;又因为本题权值为拼接字符串表示的十进制数,且是“按照顺序依次填写”,则记 表示按序填写 个字符串,择取其中 个以任意顺序摆放拼接,且第 个字符串一定选中的权值总和。答案即
的计算都是简单的背包,复杂度 ;思考如何快速计算 。
场上我试图维护“所有可能的结果字符串”的各项信息,同时直接计算暴力插入一个字串 的贡献(插入后的数可以拆分成 ,维护每个串在所有位置劈开得到的“高位”和“低位”权值和),但发现式子首项无法用线性方式维护插入 之后的贡献。
何钒佑教给我们一更聪明的贡献方法:将每一个字串的贡献独立,写作 。具体地,对于一个由 个串拼接而成的的结果串,我们将其复制成 份;对于第 份,我们预留第 位作为一个“窗口”,用第 份串计算 的贡献。若要将串 填入,我们既可以填入窗口右侧,此时对权值积的贡献为 ;也可以占用窗口本身而计算 ;也可以填入窗口左侧,贡献不变。容易验证,它的和不重不漏地计算了每一个串的每一位代价;指数相加结果相乘,它具有结合律。于是记 表示考虑了 ,择其中 个拼接,且在窗口右侧已经填入 个串,窗口是否被占用的权值总和;转移时做些处理,就能计算 。
时间复杂度 ,常数大。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
#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 – 最优化
最小费用增广流。