P6569 [NOI Online #3 提高组] 魔法值 题解
目录
题目描述
个城市, 条无向边,记第 个点第 天的魔法值为 。
给定 ,此后每天某个城市的魔法值,等于与它相邻的城市上一天魔法值的异或和,即:
次询问,给定 ,求 。
数据范围
- 。
- 。
时间限制 ,空间限制 。
分析
矩阵快速幂技巧大杂烩。
方法一
记 , 为转移矩阵,则 。
这里 定义为 矩阵乘法,即:
由于矩阵乘法需要满足结合律,第二个运算符对第一个运算符需要满足分配律。比如常见的 矩阵,我们有:
这是因为,对于 矩阵乘法:
结合律意味着可以打开内层括号,即 对 有分配律。
非常遗憾, 对 并不具有分配律,比如 。
但是本题还有一个特殊条件: 中元素仅有 和 !
读者可以自行验证
在 时成立,换言之,当 为 矩阵时, 矩阵乘法符合结合律。
直接做矩阵快速幂,时间复杂度 。
但是向量乘矩阵单次 ,如果预处理 ,询问时直接将 二进制分解,用答案向量乘上 二进制下为 的位对应的矩阵即可,时间复杂度 。
#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;
}
方法二
矩阵做矩阵快速幂,怎么能少 bitset
优化呢?
在域 下,可以将乘法视为按位与:
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;
}
暴力矩阵快速幂的时间复杂度 ,已经足够通过了,这也是下面代码中展示的做法。
如果硬要套向量乘矩阵的 ,那就需要将 拆位,时间复杂度 。由于 和 数量级相差不大,因此没有必要。
#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;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/18651290
分类:
OI & ACM / 题解
标签:
算法-压位&bitset
, 组合数学-矩阵乘法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人