peiwenjun's blog 没有知识的荒原

P6569 [NOI Online #3 提高组] 魔法值 题解

题目描述

\(n\) 个城市, \(m\) 条无向边,记第 \(i\) 个点第 \(j\) 天的魔法值为 \(f_{i,j}\)

给定 \(f_{i,0}\) ,此后每天某个城市的魔法值,等于与它相邻的城市上一天魔法值的异或和,即:

\[f_{u,j}=\bigoplus_{(u,v)\in E}f_{v,j-1}\\ \]

\(q\) 次询问,给定 \(a_i\) ,求 \(f_{1,a_i}\)

数据范围

  • \(1\le n,q\le 100,1\le m\le\frac{n(n-1)}2\)
  • \(1\le a_i\lt 2^{32},0\le f_{i,0}\lt 2^{32}\)

时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{512MB}\)

分析

矩阵快速幂技巧大杂烩。

方法一

\(F_j=\begin{bmatrix}f_{1,j}&\cdots&f_{n,j}\\\end{bmatrix}\)\(G\) 为转移矩阵,则 \(F_j=F_0\times G^j\)

这里 \(\times\) 定义为 \((\oplus,\times)\) 矩阵乘法,即:

\[C_{n,l}=A_{n,m}\times B_{m,l}\\ c_{i,j}=\bigoplus_{1\le k\le m}a_{i,k}\times b_{k,j}\\ \]

由于矩阵乘法需要满足结合律,第二个运算符对第一个运算符需要满足分配律。比如常见的 \((+,\times),(\min,+)\) 矩阵,我们有:

\[(a+b)\times c=a\times c+b\times c\\ \min(a,b)+c=\min(a+c,b+c)\\ \]

这是因为,对于 \((\star,\odot)\) 矩阵乘法:

\[((AB)C)_{i,j}=\bigstar\big((\bigstar a_{i,k}\odot b_{k,l})\odot c_{l,j}\big)\\ (A(BC))_{i,j}=\bigstar\big(a_{i,k}\odot(\bigstar b_{k,l}\odot c_{l,j})\big)\\ \]

结合律意味着可以打开内层括号,即 \(\odot\)\(\star\) 有分配律。

非常遗憾, \(\times\)\(\oplus\) 并不具有分配律,比如 \((2\oplus 5)\times 3\neq 2\times 3\oplus 5\times 3\)

但是本题还有一个特殊条件: \(G\) 中元素仅有 \(0\)\(1\)

读者可以自行验证

\[(a\oplus b)\times c=a\times c\oplus b\times c\\ a\times(b\oplus c)=a\times b\oplus a\times c\\ \]

\(b,c\in\{0,1\}\) 时成立,换言之,\(B,C\)\(01\) 矩阵时, \((\oplus,\times)\) 矩阵乘法符合结合律


直接做矩阵快速幂,时间复杂度 \(\mathcal O(qn^3\log a)\)

但是向量乘矩阵单次 \(\mathcal O(n^2)\) ,如果预处理 \(G^{2^j}\) ,询问时直接将 \(a_i\) 二进制分解,用答案向量乘上 \(a_i\) 二进制下为 \(1\) 的位对应的矩阵即可,时间复杂度 \(\mathcal O(n^3\log a+qn^2\log a)\)

#include<bits/stdc++.h>
#define ui unsigned int
using namespace std;
int m,n,q,x,y;
struct vec
{
    ui v[105];
}o;
struct mat
{
    ui v[105][105];
}g[32];
vec operator*(vec a,mat b)
{
    static vec c;
    for(int i=1;i<=n;i++)
    {
        c.v[i]=0;
        for(int j=1;j<=n;j++) c.v[i]^=a.v[j]*b.v[j][i];
    }
    return c;
}
mat operator*(mat a,mat b)
{
    static mat c;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            c.v[i][j]=0;
            for(int k=1;k<=n;k++) c.v[i][j]^=a.v[i][k]*b.v[k][j];
        }
    return c;
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++) scanf("%u",&o.v[i]);
    for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),g[0].v[x][y]=g[0].v[y][x]=1;
    for(int i=1;i<32;i++) g[i]=g[i-1]*g[i-1];
    while(q--)
    {
        scanf("%d",&x);
        vec cur=o;
        for(int i=0;i<32;i++) if(x>>i&1) cur=cur*g[i];
        printf("%u\n",cur.v[1]);
    }
    return 0;
}
方法二

\(01\) 矩阵做矩阵快速幂,怎么能少 bitset 优化呢?

在域 \(\{0,1\}\) 下,可以将乘法视为按位与:

mat operator*(mat a,mat b)
{
    mat c;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                c.v[i][j]^=a.v[i][k]&b.v[k][j];
    return c;
}

但是如果将第二维压入 bitset ,它和下述写法等价:

mat operator*(mat a,mat b)
{
    mat c;
    for(int i=1;i<=n;i++)
        for(int k=1;k<=n;k++)
            if(a.v[i][k]) c.v[i]^=b.v[k];
    return c;
}

暴力矩阵快速幂的时间复杂度 \(\mathcal O(\frac{qn^3\log a}w)\) ,已经足够通过了,这也是下面代码中展示的做法。

如果硬要套向量乘矩阵的 \(\texttt{trick}\) ,那就需要将 \(F_0\) 拆位,时间复杂度 \(\mathcal O(\frac{n^3\log a}w+\frac{32qn^2\log a}w)\) 。由于 \(n\)\(32\) 数量级相差不大,因此没有必要。

#include<bits/stdc++.h>
#define ui unsigned int
using namespace std;
int m,n,q,x,y;
ui res,f[105];
struct mat
{
    bitset<105> v[105];
    mat()
    {
        for(int i=1;i<=n;i++) v[i].reset();
    }
}g[32];
mat operator*(mat a,mat b)
{
    mat c;
    for(int i=1;i<=n;i++)
        for(int k=1;k<=n;k++)
            if(a.v[i][k]) c.v[i]^=b.v[k];
    return c;
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++) scanf("%u",&f[i]);
    for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),g[0].v[x][y]=g[0].v[y][x]=1;
    for(int i=1;i<32;i++) g[i]=g[i-1]*g[i-1];
    while(q--)
    {
        scanf("%d",&x),x--,res=0;
        mat cur=g[0];
        for(int i=0;i<32;i++) if(x>>i&1) cur=cur*g[i];
        for(int i=1;i<=n;i++) if(cur.v[i][1]) res^=f[i];
        printf("%u\n",res);
    }
    return 0;
}

posted on 2025-01-04 00:11  peiwenjun  阅读(21)  评论(0)    收藏  举报

导航