题目描述
\(n\) 个城市, \(m\) 条无向边,记第 \(i\) 个点第 \(j\) 天的魔法值为 \(f_{i,j}\) 。
给定 \(f_{i,0}\) ,此后每天某个城市的魔法值,等于与它相邻的城市上一天魔法值的异或和,即:
\(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,+)\) 矩阵,我们有:
这是因为,对于 \((\star,\odot)\) 矩阵乘法:
结合律意味着可以打开内层括号,即 \(\odot\) 对 \(\star\) 有左分配律。
非常遗憾, \(\times\) 对 \(\oplus\) 并不具有左分配律。
但是本题还有一个特殊条件: \(G\) 中元素仅有 \(0\) 和 \(1\) !
读者可以自行验证 \((a\oplus b)\times c=a\times c\oplus b\times c\) 在 \(c\in\{0,1\}\) 时成立,换言之,当 \(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;
}