Loading

[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;
}

总结

善于利用异或这种特殊运算的性质

二进制拆分可以优化这类问题

矩阵乘法没有交换律!!!

以后写矩阵乘法要严格按照两个矩阵的大小来计算, 不然就容易趋势

  • 最外层和最内层是不同尺寸, 中间层是相同的那个尺寸
posted @ 2024-12-21 09:03  Yorg  阅读(27)  评论(0)    收藏  举报