0%

LibreOJ 6343. Sally Face 与地牢 题解

题目链接:LibreOJ #6343. Sally Face 与地牢


做过 [Heoi 2014]林中路径 的同学应该対本题感到十分亲切,而在本题目,询问由平方和变为任意 \(r_i\) 次方和。

边无向和自环其实和边有向的情况完全相同,注意特判即可,关键问题为维护 \(r\) 次方和。

定义非负整数集合 \(S\) 用来存储路径长度,定义矩阵 $A_{[p]S}(0pr) $,表示从 \(i\) 点到 \(j\) 点,路径长度 \(k\in S\) 的路径长度的 \(p\) 次方和。

\(u\rightarrow k\)\(a\) 条路径,长度为 \(c_1,c_2,\dots,c_{a}(c_i\in S_1)\) , 设 \(k\rightarrow v\)\(b\) 条路径,长度为 \(d_1,d_2,\dots,d_{b}(d_i\in S_2)\) ,我们有以下式子: \[ \begin{aligned} A_{[p]S_1\times S_2}[u][v] &= \sum_{i=1}^{a}\sum_{j=1}^{b}(c_i+d_j)^p\\ &=\sum_{i=1}^a\sum_{j=1}^b\sum_{t=0}^{p}\binom{p}{t}c_i^{p-t}d_j^{t}\\ &=\sum_{t=0}^{p}\binom{p}{t}\sum_{i=1}^a\sum_{j=1}^bA_{[p-t]S_1}[u][k]\cdot A_{[t]S_2}[k][v]\\ \end{aligned} \] 其中 $ S_1S_2 $ 表示一个集合 \(S'\), 设\(S_1, S_2\) 进行笛卡尔积后为集合 \(\{(a_i,b_i)\}\),则 $ S'={a_i+b_i} $。上面的形式符合矩阵乘法的运算法则,所以有: \[ A_{[p]S_1\times S_2}=\sum_{t=0}^p\binom{p}{t}A_{[p-t]S_1}\times A_{[t]S_2} \]

开始读入路径的长度都为 $ 1 $,直接读入 $ A_{[p]{1}}[i][j] $ ,运用刚才定义的矩阵运算计算,可得 $ A^i $ 为恰好走 \(i\) 步的答案,则 \(\sum_{i=1}^kA_{[p]}^i\) 即为最多走 $ k $ 步的答案 。对于 \(k\leq 10^{13}\) ,我们运用快速幂的思想加速计算,具体细节参考下面代码中的 \(\text{procedure()}\) 函数,时间复杂度为 \(O(n^3r^2logk+q)\)

注意到由于 \(r^2\) 的存在,这个复杂度无法通过此题,我们考虑在具体实现中把 $ i $ 次方和作为 $ (r+1)$ 元组放在一个矩阵中的每个位置,则每次做元组乘法时实际上是: \[ \begin{align} &(a_0,a_1,a_2,\dots,a_r)\\ &(b_0,\,b_1,\,b_2,\dots,b_r) \end{align} \] 其中下标表示路径长度的 $ i $ 次方和,则实际上上下两个式子做了一个卷积,且卷积每次相乘附带了一个二项式系数。设 \(c_i\) 为卷积后的第 $ i $ 次方和的答案,则: \[ \begin{align} c_i&=\sum_{j=0}^ia_j\cdot b_{i-j}\cdot \binom{i}{j}\\ &=\sum_{j=0}^ia_j\cdot b_{i-j}\cdot \frac{i!}{j!(i-j)!}\\ &=i!\sum_{j=0}^i\frac{a_j}{j!}\cdot \frac{b_{i-j}}{(i-j)!} \end{align} \] 用原数除下标的阶乘代替原数做快速傅立叶变换,之后乘以 \(i!\) 就是新的值,预处理阶乘及阶乘逆元即可。

至此问题解决,复杂度为 \(O(n^3r\text{log}r\cdot \text{log}k+q)\)

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <cmath>

using namespace std;
typedef long long ll;

const int mod = 1004535809;
const int N = 10 + 5;
const int R = 5000 + 5;
const int g = 3;

template <typename _Tp>
void read(_Tp &a, char c = 0) {
for (c = getchar(); !isdigit(c); c = getchar());
for (a = 0; isdigit(c); a = a * 10 + c - '0', c = getchar());
}

int n, m, r, q;
ll rev[R], inv_n, r_n, k;
ll fac_inv[R], fac[R];

ll q_pow(ll a, ll b, ll ret = 1) {
while (b) { if (b & 1) ret = ret * a % mod; a = a * a % mod; b >>= 1; }
return ret;
}

void init() {
r_n = 1 << (ll)ceil(log2(r + 1));
r_n <<= 1;
inv_n = q_pow(r_n, mod - 2);
for (int i = 0; i < r_n; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << ((ll)log2(r_n) - 1));
fac[0] = 1;
for (int i = 1; i <= r; i++) fac[i] = (fac[i - 1] * i) % mod;
fac_inv[r] = q_pow(fac[r], mod - 2);
for (int i = r - 1; i >= 0; i--) fac_inv[i] = fac_inv[i + 1] * (i + 1) % mod;
}

void NTT(ll F[], ll _n_, int op) {
for (int i = 0; i < _n_; i++) if (rev[i] > i) swap(F[i], F[rev[i]]);
for (int i = 2; i <= _n_; i <<= 1) {
ll wi = q_pow(g, op == 1 ? (mod - 1) / i : mod - 1 - (mod - 1) / i);
for (int j = 0; j < _n_; j += i) {
ll w = 1;
for (int k = j, h = i >> 1; k < j + h; k++) {
ll t = w * F[k + h], u = F[k];
F[k] = (u + t) % mod;
F[k + h] = ((u - t) % mod + mod) % mod;
w = w * wi % mod;
}
}
}
if (op == -1) for (int i = 0; i < _n_; i++) F[i] = F[i] * inv_n % mod;
}

ll ta[R], tb[R];

void FFT(ll c[], const ll a[], const ll b[]) {
memset(ta, 0, sizeof ta);
memset(tb, 0, sizeof tb);
for (int i = 0; i <= r; i++) {
ta[i] = a[i] * fac_inv[i] % mod;
tb[i] = b[i] * fac_inv[i] % mod;
}
NTT(ta, r_n, 1);
NTT(tb, r_n, 1);
for (int i = 0; i < r_n; i++) c[i] = ta[i] * tb[i] % mod;
NTT(c, r_n, -1);
for (int i = 0; i < r_n; i++) c[i] = c[i] * fac[i] % mod;
}

struct M {
ll a[R];
M() {}
M operator + (const M &x) const {
M ret;
for (int i = 0; i <= r; i++) ret.a[i] = (a[i] + x.a[i]) % mod;
return ret;
}
M operator * (const M &x) const {
M ret;
FFT(ret.a, a, x.a);
for (int i = 0; i <= r; i++) ret.a[i] = ret.a[i] % mod;
for (int i = r + 1; i < r_n; i++) ret.a[i] = 0;
return ret;
}
};

M A[N][N], t[N][N], o[N][N], a[N][N], b[N][N];

void mul(M t1[N][N], M t2[N][N]) {
memset(t, 0, sizeof t);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++) t[i][j] = t[i][j] + t1[i][k] * t2[k][j];
memcpy(t1, t, sizeof t);
}

void add(M t1[N][N], M t2[N][N]) {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) t1[i][j] = t1[i][j] + t2[i][j];
}

void procedure(ll k) {
if (k == 1) return;
procedure(k >> 1), memcpy(b, A, sizeof A), mul(A, a), mul(a, a), add(A, b);
if (k & 1) mul(A, o), mul(a, o), add(A, o);
}

int main() {
read(n), read(m), read(r), read(k), read(q), init();
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
for (int j = 0; j <= r; j++) {
A[x][y].a[j]++, A[y][x].a[j]++;
if (x == y) A[x][y].a[j]--;
}
}
memcpy(o, A, sizeof A);
memcpy(a, A, sizeof A);
procedure(k);
while (q--) {
int x, y, z; read(x), read(y), read(z);
printf("%lld\n", A[x][y].a[z]);
}
return 0;
}

\(n\) 年前某天在机房里出的题,结果std被同学们暴打,顶锅盖,逃……