【题解】LC:带权并查集(非交换群)(Unionfind with Potential (Non-Commutative Group))

有矩阵乘法我无疑是兴奋的。

并查集家族新增一名猛将。

请先保证这个会了喵:【代码】带权并查集模板-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;
} 
相关推荐
一去不复返的通信er3 分钟前
力扣15:盛最多水的容器
算法·leetcode
努力实干5 分钟前
第二阶段 · 第 7 课:C++ 多线程与线程安全
开发语言·c++·安全
Navigator_Z6 分钟前
LeetCode //C - 1309. Decrypt String from Alphabet to Integer Mapping
c语言·算法·leetcode
潇湘秦9 分钟前
SQL Server 2016+ 整数类型元数据扩宽(秒级变更)完整说明
数据库·算法
Figo_Cheung1 小时前
Figo基于结构空间算法假设(SSAH)的生成式大模型优化范式与工程实现边界研究——提出适应AGI架构演化的自适应拓扑感知表征机制
算法·架构·agi
小菜要扣篮1 小时前
Ubuntu 下使用 vscode 打开 C++ 项目后,无法通过 F12 定位到定义问题解决
c++·vscode·ubuntu
wuminyu1 小时前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
旖旎夜光2 小时前
LCR 170:交易逆序对的总数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
C++ 老炮儿的技术栈2 小时前
Linux 动态库踩坑:明明重新编译了 .so,调用函数返回值却一直不变(libfjrobot.so 版本号异常排查实录)
linux·c语言·网络·c++·visual studio
微尘hjx10 小时前
【ByteTrack 04】多轨迹算法ByteTrack改进点
算法·yolo·目标检测·目标跟踪·bytetrack·ultralytics·车跟踪