广义矩阵乘法显然满足结合律
phil071128 · · 题解
前言:事实上,本题想到构造广义矩阵乘法的过程并不容易,而题解几乎都是简单给出最终做法,而并没有思维过程。更重要的,广义矩阵乘法必须要满足结合律,这一点实际上是必须要证明且不可忽略的。感觉本题的精髓并不在向量 trick 上,而是对广义矩阵乘法结合律的证明。
一点小思维,看到异或按位考虑。但是初始可能会想模拟找规律,不过可以发现相当于每一位
这说明不应该按位考虑,套路的,把 32 位 int 状压起来,那么乘 (xor,and) 矩阵,我们最后会证明它有结合律。
对于多次询问同一
每次询问,我们直接对指数二进制拆开,相当于初始矩阵乘以
证明 (xor,and) 广义矩阵乘法有结合律:
交换求和下标,即证:
回忆我们如何证明 (+,*) 矩阵的:
而右手边也可以拆成这样的形式,这样做的原因在于乘法对加法有分配率。那么,只需证
-
按位考虑,如果
c=0 :此时
a,b 无论如何取,左右两边均为0 。 -
如果
c=1 :此时,当且仅当
a,b 不同是为1 ,否则为0 。可以很简单地验证。
因此:
右手边同理。
upd:实际上,与运算就是 (+,*) 矩阵是一样的,所以证明也不是那么必要。
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
int read(){
char c=getchar();int h=0,tag=1;
while(!isdigit(c)) tag=(c=='-'?-1:1),c=getchar();
while(isdigit(c)) h=(h<<1)+(h<<3)+(c^48),c=getchar();
return h*tag;
}
void fil(){
freopen("P6569_2.in","r",stdin);
freopen("data.out","w",stdout);
}
const int U=(1ll<<32ll)-1;
struct mat{
int a[100][100];
mat () {
memset(a,0,sizeof a);
}
void init() {
for(int i=0;i<100;i++) a[i][i]=U;
}
int * operator [] (int x) {
return a[x];
}
mat operator * (mat b) {
mat c;
for(int k=0;k<100;k++) {
for(int i=0;i<100;i++) {
for(int j=0;j<100;j++) {
c.a[i][j]^=(a[i][k]&b.a[k][j]);
}
}
}
return c;
}
}trans,mi[333];
struct vec{
int a[100];
vec() {
memset(a,0,sizeof a);
}
vec operator * (mat b) {
vec c;
for(int k=0;k<100;k++) {
for(int j=0;j<100;j++) {
c.a[j]^=(a[k]&b.a[k][j]);
}
}
return c;
}
}v0,v;
mat ksm(mat a,int b) {
if(b==1) return a;
mat s=ksm(a,b/2);s=s*s;
if(b%2==1) s=s*a;
return s;
}
signed main(){
// fil();
int n=read(),m=read(),q=read();
for(int i=1;i<=n;i++) {
int x=read();
v0.a[i-1]=x;
}
for(int i=1;i<=m;i++) {
int u=read(),v=read();
trans[u-1][v-1]=trans[v-1][u-1]=U;
}
for(int i=0;i<=31;i++) mi[i]=ksm(trans,1ll<<i);
for(int i=1;i<=q;i++) {
int x=read();
v=v0;
for(int j=31;j>=0;j--) {
if(x>=(1ll<<j)) {
v=v*mi[j];x-=(1ll<<j);
}
}
cout<<v.a[0]<<"\n";
}
return 0;
}