有矩阵乘法我无疑是兴奋的。
并查集家族新增一名猛将。
请先保证这个会了喵:【代码】带权并查集模板-CSDN博客

都说非交换群了,那么本题的矩阵乘法顺序就是我们需要注意的。
我们假定题目 中
是父节点,核心顺序就是
。
即乘法顺序是:先经过父节点的变换,再经过自己的变换。
还有个问题

这时候需要逆矩阵,即原矩阵^{-1}。
只有方阵 (n×n) 且行列式不为 0 矩阵才有逆矩阵,因为不是方阵不能逆,行列式为 0 张不出积。
当然本题的 2 * 2 矩阵的逆很好求,请看推导过程。
设 是单位矩阵。


这个 中的
是矩阵的行列式,而我们题目中强调了行列式必为 1。

其他详见注释代码,我咋感觉写了这么多都没注释有用?
cpp
// by hansang / proMatheus
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL P = 998244353;
const int N = 2e5 + 10;
struct Matrix {
LL a[2][2];
Matrix() {
memset(a, 0, sizeof(a));
}
} d[N];
Matrix operator*(Matrix na, Matrix nb) {
Matrix nc;
for (int k = 0; k <= 1;k ++) {
for (int i = 0; i <= 1; i ++) {
for (int j = 0; j <= 1; j ++) {
nc.a[i][j] = (nc.a[i][j] + na.a[i][k] * nb.a[k][j] % P) % P;
}
}
}
return nc;
}
bool operator==(Matrix na, Matrix nb) {
for (int i = 0; i <= 1; i ++) {
for (int j = 0; j <= 1; j ++) {
if (na.a[i][j] != nb.a[i][j]) {
return false;
}
}
}
return true;
}
Matrix get_inv(Matrix no) {
Matrix ne;
ne.a[0][0] = no.a[1][1];
ne.a[0][1] = -no.a[0][1] + P;
ne.a[1][0] = -no.a[1][0] + P;
ne.a[1][1] = no.a[0][0];
return ne;
}
int fa[N];
int findfa(int x) {
if (fa[x] == x) {
return fa[x];
}
int to = findfa(fa[x]);
d[x] = d[fa[x]] * d[x]; // 从父亲到孩子,从左到右
return fa[x] = to;
}
int main () {
ios::sync_with_stdio(false);
cin.tie(0);
int n, Q;
cin >> n >> Q;
for (int i = 0; i < n; i ++) { // 阴没边了 base-0
fa[i] = i;
for (int j = 0; j <= 1; j ++) {
d[i].a[j][j] = 1; // 单位矩阵
}
}
while (Q --) {
int opt;
cin >> opt;
int u, v;
cin >> u >> v;
int fu = findfa(u), fv = findfa(v);
if (opt == 0) {
Matrix t;
for (int i = 0; i <= 1; i ++) {
for (int j = 0; j <= 1; j ++) {
cin >> t.a[i][j];
}
}
if (fu != fv) {
fa[fv] = fu;
d[fv] = d[u] * get_inv(t) * get_inv(d[v]);
// 关于矩乘等号移位规则:
// a * b = c * d
// 右边的移到右边
// a * b * d^{-1} = c
// a = c * d * c^{-1}
// 左边移到左边
// c^{-1} * a * b = d
// b = a^{-1} * c * d
// 反正记住乘号两边不能随意换位置就是了
// d[u] = d[v] * t
// 这里的 fv 因为还是并查集的头目,所以 d[fv] 是单位矩阵
// 我们通过 d[u] 和 d[v] 的关系重塑 d[fv]
// d[fv] = d[u] * t^{-1} * d[v]^{-1}
// 从左到右执行:
// 第 1 步:应用反向约束,从 u 到 v
// 第 2 步:撤销 d[v] 的贡献,从 v 到 v 的根 fv
// 为什么是这个顺序?
// 因为 d[fv] * d[v] * t = d[fu](这玩意是单位矩阵要不要都行) * d[u]
// 你把等式左边的从右到左,按照移位规则移到右边去
cout << "1\n";
}
else {
if (d[v] * t == d[u]) {
cout << "1\n";
}
else {
cout << "0\n";
}
}
}
else {
if (fu != fv) {
cout << "-1\n";
}
else {
// d[u] = d[v] * t
// d[v]^{-1} * d[u] = t
Matrix t = get_inv(d[v]) * d[u];
for (int i = 0; i <= 1; i ++) {
for (int j = 0; j <= 1; j ++) {
cout << t.a[i][j] << " ";
}
}
cout << "\n";
}
}
}
return 0;
}