[NOI Online #3 提高组] 魔法值
思路
讲真现在脑子胡的依托
转化题意, 给定一个无自环无重边的 \(n\) 点 \(m\) 边的图, 每天每个城市的魔法值都会变成其连边城市的魔法值的 \(\oplus\) , 求 \(a_i\) 天后, \(H\) 点的魔法值
看到题目给的柿子和 \(a_i \leq 2^{32}\) 感觉就要用矩快
这是一种异或矩快, 也就是在 \(\mod 2\) 意义下的加法运算, 知道这个了, 这个题就相对简单了
那么还是像之前的一道题一样, 我们首先列出转移矩阵
\[\begin{bmatrix}
f_{1, j - 1}\\
f_{2, j - 1}\\
f_{3, j - 1}\\
\vdots \\
f_{n, j - 1}\\
\end{bmatrix}
\cdot
base
=
\begin{bmatrix}
f_{1, j}\\
f_{2, j}\\
f_{3, j}\\
\vdots \\
f_{n, j}\\
\end{bmatrix}
\]
计算可知,
\[base =
\begin{bmatrix}
1 & 1 & 0 & 1 \cdots 1 \\
1 & 1 & 0 & 1 \cdots 1 \\
1 & 1 & 0 & 1 \cdots 1 \\
\vdots & \vdots & \vdots & \ddots
\end{bmatrix}
\]
其中 \(0, 1\) 由是否连通决定, 其实就是一个邻接矩阵
这样子做是朴素的, 复杂度是 \(\mathcal{O} (q \log a_i \omega)\) , 其中 \(\omega = n^3\)
这个基本上是过不去的, 怎么优化?
我们注意到, 多次询问直接搞一个二进制拆分即可, 传统 \(\rm{trick}\)
在这里对这种异或的矩阵乘法做一些理解
首先我们知道, 对于我们最初列出来的柿子
\[\begin{bmatrix}
f_{1, j - 1}\\
f_{2, j - 1}\\
f_{3, j - 1}\\
\vdots \\
f_{n, j - 1}\\
\end{bmatrix}
\cdot
base
=
\begin{bmatrix}
f_{1, j}\\
f_{2, j}\\
f_{3, j}\\
\vdots \\
f_{n, j}\\
\end{bmatrix}
\]
这个东西, \(base\) 的本质其实是简化了一个线性方程组, 如果 \(base\) 当位为 \(1\) , 那么就需要异或上这个值, 如果为 \(0\) 就跳过
那么如何优化 \(base\) 的计算 , 总不能一个一个乘上去, 还是考虑用快速幂
用快速幂要证明在这样的情况下, 矩阵乘法仍然满足结合律, 你发现他确实符合, 证明略
一定要注意没有交换律
实现
框架
先搞出 \(base\) , 然后按部就班的做即可
代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 120;
const int N = 100;
const int MOD = 2;
int n, m, q;
int add(int a, int b) { return (a + b > MOD) ? (a + b) % MOD : a + b; }
int mul(int a, int b) { return (a * b * 1ll) % MOD; }
int dec(int a, int b) { return (a - b < 0) ? (a + MOD - b) % MOD : a - b; }
void pluson(int& a, int b) { a = add(a, b); }
struct matrix {
int M[MAXN][MAXN];
void clear() {
memset(M, 0, sizeof M);
}
void reset() {
clear();
for (int i = 1; i <= N; i++) M[i][i] = 1;
}
} ;
matrix operator * (const matrix& a, const matrix& b) {
matrix ans;
ans.clear();
for (int i = 1; i <= n; i++)
for (int k = 1; k <= n; k++)
for (int j = 1; j <= n; j++)
ans.M[i][j] ^= a.M[i][k] * b.M[k][j];
return ans;
}
matrix f, base;
matrix pows[33]; // 幂次幂 base
/*预处理 2^k*/
void init()
{
pows[0] = base;
for (int i = 1; i <= 32; i++) {
pows[i] = pows[i - 1] * pows[i - 1];
}
}
signed main()
{
// freopen("P6569_2.in", "r", stdin);
// freopen("my.out", "w", stdout);
scanf("%lld %lld %lld", &n, &m, &q);
f.clear(), base.clear();
for (int i = 1; i <= n; i++) {
int now;
scanf("%lld", &f.M[i][1]);
}
for (int i = 1; i <= m; i++) {
int u, v; scanf("%lld %lld", &u, &v);
base.M[u][v] = 1, base.M[v][u] = 1;
}
init();
for (int i = 1; i <= q; i++) {
int nowc; scanf("%lld", &nowc);
matrix nowp; nowp = f;
for (int j = 32; j >= 0; j--) {
if ((nowc >> j) & 1) {
nowp = pows[j] * nowp;
}
}
printf("%lld\n", nowp.M[1][1]);
}
return 0;
}
总结
善于利用异或这种特殊运算的性质
二进制拆分可以优化这类问题
矩阵乘法没有交换律!!!
以后写矩阵乘法要严格按照两个矩阵的大小来计算, 不然就容易趋势
- 最外层和最内层是不同尺寸, 中间层是相同的那个尺寸

浙公网安备 33010602011771号