潜龙未见静水流,沉默深藏待时秋。一朝破空声势振,惊世骇俗展雄猷。
随笔 - 80, 文章 - 0, 评论 - 3, 阅读 - 2084

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

目录

题目描述

n 个城市, m 条无向边,记第 i 个点第 j 天的魔法值为 fi,j

给定 fi,0 ,此后每天某个城市的魔法值,等于与它相邻的城市上一天魔法值的异或和,即:

fu,j=(u,v)Efv,j1

q 次询问,给定 ai ,求 f1,ai

数据范围

  • 1n,q100,1mn(n1)2
  • 1ai<232,0fi,0<232

时间限制 1s ,空间限制 512MB

分析

矩阵快速幂技巧大杂烩。

方法一

Fj=[f1,jfn,j]G 为转移矩阵,则 Fj=F0×Gj

这里 × 定义为 (,×) 矩阵乘法,即:

Cn,l=An,m×Bm,lci,j=1kmai,k×bk,j

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

(a+b)×c=a×c+b×cmin(a,b)+c=min(a+c,b+c)

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

((AB)C)i,j=((ai,kbk,l)cl,j)(A(BC))i,j=(ai,k(bk,lcl,j))

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

非常遗憾, × 并不具有分配律,比如 (25)×32×35×3

但是本题还有一个特殊条件: G 中元素仅有 01

读者可以自行验证

(ab)×c=a×cb×ca×(bc)=a×ba×c

b,c{0,1} 时成立,换言之,B,C01 矩阵时, (,×) 矩阵乘法符合结合律


直接做矩阵快速幂,时间复杂度 O(qn3loga)

但是向量乘矩阵单次 O(n2) ,如果预处理 G2j ,询问时直接将 ai 二进制分解,用答案向量乘上 ai 二进制下为 1 的位对应的矩阵即可,时间复杂度 O(n3loga+qn2loga)

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

暴力矩阵快速幂的时间复杂度 O(qn3logaw) ,已经足够通过了,这也是下面代码中展示的做法。

如果硬要套向量乘矩阵的 trick ,那就需要将 F0 拆位,时间复杂度 O(n3logaw+32qn2logaw) 。由于 n32 数量级相差不大,因此没有必要。

#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   peiwenjun  阅读(5)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人

导航

< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5
点击右上角即可分享
微信分享提示