零、目录
- 线性基
- 线性空间
- 行列式
- 矩阵树定理
- 矩阵求逆
一、线性基
线性基是数据结构啊,可是既然它在线性代数题单里,那它就是线性代数吧。
事实上确实可以用接近于线性代数的思维理解。
DS 滚出数学。
引入
考虑如下的问题:
给定 $n$ 个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
$ 1 \leq n \leq 50, 0 \leq S_i < 2 ^ {50} $。
线性基是一种擅长处理异或问题的数据结构。
首先考虑异或的本质:不进位加法。
考虑把一个数(例如 $5$)写成二进制形式:$(0101)_2$。
然后把它写成向量形式:$\{0, 1, 0, 1\}$。
然后两个数 $x,y$ 异或,相当于它们每一位做不进位加法,有相当于它们的向量相加,但是每一维都在 $\bmod 2$ 意义下。
回到本题:
相当于现在有 $n$ 个向量 $v_1, v_2, \dots, v_n$,需要给每个向量赋值 $a_i$ 为 $0$ 或 $1$,计算 $a_1 v_1 + a_2 v_2 + \dots + a_n v_n$ 的所有可能结果和字典序最大的结果。
线性基的思想就是找到一组基,使得它能表示出的向量与 $\{ v_1, v_2, \dots, v_n \}$ 能表示出的向量集合是一样的。
举一个特殊例子:已知 $\{x,y\}$,它能表示出的有 $\{0, x, y, x \oplus y\}$。然而 $\{x, x \oplus y\}$ 也恰好表示出这个集合,因此它是一组合法的基。
也就是 $a_i \oplus a_j$ 之后,这个序列能表示的集合不变,此时我们不妨称这样的操作为一次等价变换。
线性基就是不断通过等价变换,让这些向量变得尽量简单或有规律。
如何让它变得有规律?线性基通过限制基的最高位互不相同解决了这件事情。
具体地,假设现在基中已有元素 $10010$ 和 $01001$,现在要插入 $11101$。
由于当前插入的元素做任意次等价变换,表示的集合依然不变,所以可以把它和基中的元素异或。
- 第一轮:最高位已经在基内,执行 $11101 \oplus 10010 = 01111$。
- 第二轮:最高位已经在基内,执行 $01111 \oplus 01001 = 00110$。
- 第三轮:最高位不在基内,直接把 $00110$ 插入线性基即可。
因此我们在执行插入操作的时候只关心最高位是否出现。
简单操作 & Code
学会了思路之后代码是好写的。
记录 base[i]
表示以 $i$ 为最高位的基底元素。
按照之前的插入流程模拟即可,时空复杂度均为 $O(\log n)$。
设位数为 $m$,若位数很多则复杂度为 $O(\frac{m^2}{\omega})$,因为要遍历每一位,还要异或,可以用 bitset 优化。
bool chk(long long x, int i) { return (x >> i) & 1; }
void insert(long long x) {
for (int i = 50; i >= 0; i--) {
if (!chk(x, i)) continue;
if (!base[i]) return base[i] = x, void();
x ^= base[i];
}
}
其实这个过程很像高斯消元,类似于消成上三角形式,也就是把对角线下面全部异或为 $0$。
求异或可得最大元素
类似于 Trie 树上贪心二分,从高往低能取 $1$ 就取 $1$。
long long querymx() {
long long res = 0;
for (int i = 50; i >= 0; i--) res = max(res, res ^ base[i]);
return res;
}
求 $x$ 是否可以被表示为异或和
也是贪心,尽量异或掉多的位。
如果最后被消成 $0$ 说明可以被表示。
bool querycan(long long x) {
for (int i = 50; i >= 0; i--) x = min(x, x ^ base[i]);
return (x == 0ll);
}
例 1:HDU3949 XOR
给定 $n$ 个正整数 $a_1, a_2, \dots, a_n$,求这些数异或表示出的数的集合中,第 $k$ 小的是多少(不能选空集)。
事实上和模板几乎一样。
还是考虑从高位到低位贪心确定,首先如果基内有 $c$ 个元素,那么它们可以组合出 $2^c$ 种数。
那么我们只要考虑 $k$ 和 $2^{c-1}$ 的关系就知道这一位是 $0$ 还是 $1$ 了。
具体地,设到当前确定的数为 $x$,到了第 $u$ 位,接下来还有 $c$ 个基要处理(因为有一些位的基可能不存在所以要记录 $c$)。
如果 $x$ 的第 $u$ 位已经是 $1$,那么选上这一位的基底会让 $x$ 更小,不选会更大,根据 $k$ 属于哪边递归求解。
这样写代码真的很丑……但有一种更优雅的做法可以让代码变得较为简单。
考虑按照之前的方法求出的基如何再简化:相当于拿高位的基再和低位的基异或,把高位基消掉尽可能多低位的 $1$。
说着挺绕的,但事实上按照高斯消元的思维模式很好理解:
10011
01110
00110
00001
这样的一组基,我们把上面的不断异或掉下面的,就会得到:
10010
01000
00110
00001
这样转化有什么用?
这样每一组基的最高位,在矩阵上的位置设为 $(x,y)$,则 $(x,y)$ 上方一定都是 $0$,因为它之前的基一定可以通过异或它把这一位消掉。
相当于少一层分讨,因为不需要特判 $x$ 的第 $u$ 位是否是 $1$ 了,选了一定更大,不选一定更小。
因此将询问 $k$ 的二进制表示为 $1$ 的那些基底异或起来就是答案。
因为之前 $mid = 2^{c-1}$,所以要把全 $0$ 行删掉再给 $k$ 做二进制分解,也就是删掉 base 中为 $0$ 的元素。
由于不能选空集,还需要特判线性基中的元素能否构成 $0$。
这是简单的,可以用 map 维护,但是更省事的方法是:如果一个元素插入线性基失败,说明它可以被表示为之前元素的异或和,因此一定有一种方案使得选出的元素异或和为 $0$。
Code.
例 2:洛谷 P4151 WC2011 最大XOR和路径
好神秘的题目。
看到异或和最大可以联想到 Trie 树?不对啊这题怎么 Trie 都不是很正经。
但这正是线性基擅长处理的问题类型。
发掘异或的性质:对于这张图上的环,我们一定可以任意决定它选或不选。
为什么?考虑先从起点 $1$ 开始搜一下 dfs 树,则非树边都是返祖边,且它和一部分树边构成环。
如果我们需要选一个环,可以考虑从 $1$ 开始,先到达环的顶部,然后沿着树边走到环底部,再通过返祖边绕回来,最后回到起点 $1$,此时产生的异或和就是环异或和。
但是需要 $1$ 走到 $n$,那就是 dfs 树上 $1 \to n$ 的异或和了。
求异或最大值,只需要把所有返祖边构成的环扔进线性基,查询 $dis_n$ 即树上异或和能构成的最大数。
环的数量等于返祖边数量,是 $O(m)$ 级别的;线性基也就多一个 $\log$,可以通过。
Code.
例 3:CF251D Two Sets
已知一个序列 $a_1,a_2, \dots, a_n$,把它分成两个集合 $S_1,S_2$,使得两个集合内元素异或和之和尽量大,在使得这个值最大的前提下让 $S_1$ 内的异或和尽量小,求具体的分配集合方案。
考虑异或对答案有什么具体的影响。
首先全集 $a$ 是已知的,所以全集异或和等于 $S_1,S_2$ 异或和也是已知的,我们记它为 $sum$。
若 $sum$ 第 $i$ 位为 $1$,则无论如何分配 $S_1,S_2$ 这一位一定至少有一个 $1$,所以它对答案是没有影响的。
若 $sum$ 第 $i$ 位为 $0$,则这一位在 $S_1,S_2$ 中可能是 $0,0$ 也可能是 $1,1$。
下文称这两种分别为“$0$ 位”和“$1$ 位”,考虑把无影响的 $1$ 位和有影响的 $0$ 位分开计算。
但是 $0,1$ 位内部是有权值大小顺序的。
综上我们构造出这样一种合法的转化:
设高位为“前面”,低位为“后面”。
则我们把所有 $0$ 位按照从高到低的顺序放在前面,所有 $1$ 位按照从高到低的顺序放在后面。
这样转化可以保证位之间的优先贪心顺序,而且能转化为更加便于贪心的形式。
至于构造方案,只需要记录线性基内每一个元素由原序列哪些元素异或得到即可。
Code.
例 4:CF895C Square Subsets
给定长度为 $n$ 的序列 $a$,求有多少种选非空集合的方式可以使得选出的集合内,元素之积是完全平方数。
简单题。
容易想到给每个 $a_i$ 质因数分解,观察到值域 $\leq 70$,所以质因子数量很少。
然后完全平方数相当于每个指数幂指数都为偶数,所以考虑记录 $\bmod 2$ 意义下的指数。
乘积相当于指数相加,体现在 $\bmod 2$ 意义下就是异或,求有多少种选集合方法使得异或和位 $0$,这很容易想到用线性基维护。
若有 $c$ 个元素插入失败,则答案显然为 $2^c - 1$。因为插入失败的元素随便选,一定有唯一的线性基元素选或不选的状态和它对应;要 $-1$ 因为不能选空集。
例 5:洛谷 P3292 SCOI2016 幸运数字
给定一棵 $n$ 个点的树,点有点权 $\lt 2^{60}$,$q$ 次询问路径 $(u,v)$ 上任选点权,异或得到最大的权值。
看到选择元素,使得异或和最大自然想到线性基。
然后这是路径,考虑和主席树一样可持久化……可持久化线性基?开始玄幻了。
如果记录每个点到根的线性基呢?但线性基不能执行删除操作,也做不了。
……
既然不能删除那就直接合并。
暴力遍历路径上每个点然后扔进线性基。这显然不对。
能否通过记录一些额外的信息使得查询速度更快?例如多维护一些线性基。
那得先考虑如何合并两个线性基?显然直接暴力把一个线性基中的元素插入另一个线性基即可。
然后我们做一个类似于求 LCA 的事情:倍增。
对于每个点,我们存储它往上跳 $[0,2^k)$ 步到达点的集合的线性基,这样空间复杂度 $O(n \log n \log V)$。
查询就直接暴力跳倍增即可,这样总时间复杂度是 $O((n + q) \log n \log^2 V)$。
看上去有点悬。
复杂度瓶颈在哪里?在于询问的次数 $q$ 比较大。
但异或有一个很优美的性质:“对消”——$x \oplus x = 0$。
因此这类似于 RMQ 问题,对于 RMQ 的查询 $[l,r]$,只需要拿出 $[l,l+2^k)$ 和 $(r-2^k,r]$ 的倍增结果 $O(1)$ 合并即可。
那么对于 $(u,lca)$ 这条路径也一样,做一个类似于 ST 表的操作就可以 $O(\log^2 V)$ 合并出答案,另外一边 $(v,lca)$ 同理。
这样总复杂度就降到了 $O(n \log n \log^2 V + q \log^2 V)$。
Code.
例 6:洛谷 P4570 BJWC2011 元素
给定若干个二元组 $(a_i, b_i)$,要求选出一个集合 $S$。满足 $S$ 的 $b_i$ 之和最大,且满足不存在 $S$ 的一个非空子集 $S’$ 使得 $S’$ 内 $a_i$ 的异或和为 $0$。
顺便提一下,不存在异或和为 $0$ 称为线性无关。
贪心地,对于一个相同的 $a$ 我们肯定取最大的 $b$。
然后异或和为 $0$ 联想到使用线性基。
能否 DP,问题来了这题无论怎样设计状态都不像可以优化的样子。
考虑一个集合 $S$,它内部 $a_i$ 异或和为 $0$ 且不存在一个非空子集 $S’$ 满足 $S’$ 的异或和为 $0$。
那么只要从 $S$ 中任意删除一个元素就可以满足条件。
贪心地,肯定删除最小的元素。
把这个结论反过来做,就是把二元组按照 $b$ 排序,能加入线性基就加入。
类似于 MST 的 Kruskal 求解过程,只要加入不会形成环就加入。
例 7:洛谷 CF1100F Ivan and Burgers
给定一个长度为 $n$ 的序列 $a$,$q$ 次询问 $[l,r]$ 内选数的最大异或和。
$n, q \leq 5 \times 10^5, a_i \leq 10^6$。
3s,512 MB
很简单啊,直接 RMQ 就做完了。
然后太冲动了直接 Coding 所导致的忘记算空间了。
ST 表再内套一个线性基直接 $O(n \log n \log V)$,空间复杂度起飞了,算了下大概 700 多 MB 的样子。
于是交上去喜提 MLE On Test #1,老实了。
正解完全没有半点头绪,于是开始卡空间,然后发现这玩意儿不能开 short……
但是注意到如果可以时间换空间那就很强了,因为 $5 \times 10^8$ 空间开不下但可以跑得过。
去问了一下,发现原来是我没学过猫树分治 (lll¬ω¬)
。
可能和网上的版本有些差别?因为没有仔细学,了解思想之后直接现推了。
ST 表能解决的问题猫树都能解决,不同于 ST 表的倍增,猫树的基本思想是分治,大致可以理解为是 ST 表和线段树的结合。
由于结合了 ST 表,它一般不支持修改操作,但可以通过离线等方式快速处理询问,再拼接成答案。
具体地,它的分治树长得和线段树几乎一样,对于一次询问 $[l,r]$,我们需要寻找一个树上区间 $[L,R]$,使得 $L \leq l \leq mid \lt mid + 1 \leq r \leq R$。
然后对于树上的每个区间 $[L,R]$,考虑记录它的前后缀答案,对于一次询问只需要合并 $[L,mid]$ 和 $[mid+1,R]$ 两个区间的后缀、前缀答案。
对于这题,考虑把问题离线下来,先找到 $[l,r]$ 对应的 $[L,R]$ 把询问插入这个区间,然后对每一个区间计算前后缀的线性基。
由于猫树深度是 $O(\log n)$ 的,所以这样做的复杂度是 $O(n \log n \log V)$,$\log V$ 是线性基插入元素复杂度。
查询可以直接合并两个线性基,做到 $O(\log^2 V)$。
感觉写的会非常抽象……因为没有看过猫树的正统写法,随手胡的代码。
事实上并不抽象,甚至几乎一遍过。
Code.
二、线性空间
不是,学这玩意儿到底有啥用啊。
定义 & 引入
OI-Wiki 虽然定义得严谨,但是看上去好神秘啊。
并不严谨但比较通俗地说,之前线性基元素通过异或能表示的数的集合就是一个线性空间。
二维空间、三维空间实际上都是都是线性空间。即 $(x,y)$ 向量可以做加法、数乘运算,得到二维空间、三维空间。
之前的线性基事实上就是 $k$ 维的基,类似于二三维空间,但运算在 $\bmod 2$ 意义下进行。
类似地,可以把 $\bmod 2$ 推广到 $\bmod m$,即 $m$ 进制数,每一位 $\in [0,m)$。
对于一组基 $\{ v_1, v_2, \dots, v_k \}$,也可以通过和之前二进制类似的方法求出 $a_1v_1 + a_2v_2 + \dots + a_kv_k$ 的最大值(不断消低位)。
相关概念
线性无关、线性相关
如果 $a_1v_1 + a_2v_2 + \dots + a_kv_k = 0$ 的解只有 $a_1=a_2=\dots=a_k=0$,即零解,则我们称其为线性无关的,所以基一定是线性无关的。
若有 $v_i$ 能被其它 $v$ 表示出,则称其为线性相关的。
极大线性无关组、秩
换一个角度理解线性相关这件事情。回顾在向线性基内插入元素的流程,如果一个元素始终没有找到对应的 base 插入,那么它就一定可以被表示为之前插入的元素的异或和,此时若强行把它扔进线性空间,那么就成了线性相关。
考虑不断删除这些可以被其他元素表示的元素,直到线性空间变得线性无关,我们称其为极大线性无关组。
显然,某种意义上线性基就是一个极大线性无关组,它的大小就是秩,相当于是“维数”。
三、行列式
这个又有什么道理要学它啊。
定义
对于一个 $ n \times n $ 的矩阵 $ A $:
$$ A = \begin{bmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{k,1} & a_{k,2} & \cdots & a_{k,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \end{bmatrix} $$
行列式的值就是:
$$|A| = \sum_{p} (-1)^{\tau(p)} \prod_{k=1}^{n} a_{k,p_k}$$
其中 $p$ 是 $1$ 到 $n$ 的排列,$\tau(p)$ 表示 $p$ 的逆序对数量。
也就是枚举所有的排列 $p$,然后把第 $k$ 行第 $p_k$ 列的所有元素乘起来,根据 $p$ 的逆序对数量决定系数是 $-1$ 还是 $1$。
性质
性质 1
矩阵任意一行或任意一列 $\times t$,则行列式的值 $\times t$。
根据值的定义式,一定有且仅有一个 $a_{k,p_k}$ 被 $\times t$,所以最后行列式的值 $\times t$。
性质 2
交换矩阵任意两行或任意两列,行列式的值取反。
假设交换第 $i$ 行和第 $j$ 行,对于交换列推导类似。
相当于是交换排列中的 $p_i, p_j$。
对于后面乘积部分是不会影响的,因为元素只发生了交换,没有修改。
所以考虑 $\tau(p)$,即逆序对奇偶性的改变。
首先对于 $i,j$ 本身的交换,逆序对一定恰好变化 $1$。
然后考虑 $i,j$ 与其它数构成的逆序对,只有在 $(i,j)$ 区间内的数可能会影响,推导可以得到它们的变化量为偶数。
因此逆序对奇偶性一定变化,行列式的值取反。
性质 3
若矩阵存在任意两行相同,或任意两列相同,则行列式的值为 $0$。
性质 2 的推论,交换后为相反数,但是又相同,所以是 $0$。
性质 4
若矩阵存在任意两行或任意两列,所有元素成比例,则行列式的值为 $0$。
性质 3 结合性质 1,对其中一行或一列进行等比例扩大或缩小,就可以得到两行或两列相同了。
性质 5
若矩阵 $A$ 的第 $i$ 行(列)等于 $m$ 个矩阵 $B_1,B_2, \dots, B_m$ 第 $i$ 行(列)之和,其它位置都相等,那么 $|A| = \sum\limits_{i=1}^{m} |B_i|$。
考虑 $m=2$,有 $|A| = |B_1| + |B_2|$,再推广到 $m \gt 2$ 个矩阵的情况。
按照定义式把第 $i$ 行单独提出来,然后再分配律合并 $B_1,B_2$ 的式子就可以得到 $A$ 的式子。
性质 6
若把矩阵某一行所有元素 $\times t$ 之后再加到另一行,则行列式的值不变;列同理。
假设是 $A$ 矩阵第 $i$ 行的 $\times t$ 之后加到第 $j$ 行,那么:
根据上一条性质,构造另一个矩阵 $B$,使得第 $j$ 行是 $A$ 矩阵第 $i$ 行的元素集体 $\times t$ 的结果,其它行都一样。
那么相当于是求证 $|A|=|A|+|B|$。
事实上 $|B|$ 显然为 $0$,因为 $B$ 中第 $i$ 行和第 $j$ 成比例,根据性质 4 它的值为 $0$。
性质 7
定义余子式:设矩阵 $A$ 删掉第 $i$ 行第 $j$ 列之后的矩阵是 $B_{i,j}$,称其为余子式。
定义代数余子式:$C_{i,j} = -1^{i+j} B_{i,j}$。
对于矩阵 $A$ 任意一行或任意一列,计算所有元素与它对应代数余子式之积的和,得到的结果等于行列式的值。
假定矩阵是 $n \times n$ 的,我们对矩阵 $A$ 的第 $i$ 行进行计算,列同理。
考虑把 $A$ 拆成 $n$ 个矩阵 $M(1),M(2),\dots,M(n)$,使得每个矩阵除了第 $i$ 行都和 $A$ 一样,且满足第 $j$ 个矩阵 $M(j)$ 的第 $i$ 行除了 $M(j)_{i,j} = A_{i,j}$,其它都为 $0$。
显然根据性质 $5$,矩阵 $A$ 行列式的值等于 $\sum\limits_{i=1}^{n} |M(i)|$,所以我们可以分别对每个 $M(i)$ 计算贡献。
考虑其中第 $j$ 个矩阵的行列式有何变化。
根据行列式的计算公式:$|A| = \sum_{p} (-1)^{\tau(p)} \prod_{k=1}^{n} a_{k,p_k}$,由于 $a$ 的第 $i$ 行只有 $a_{i,j}$ 有值,其它都为 $0$,所以只有 $p_i=j$ 的时候有贡献。
相当于强制 $p_i=j$。
强制 $p_i=j$ 之后就要计算前面 $-1$ 次幂的取值了。
我们只计算和 $i$ 这一位相关的逆序对数量。
假设我们交换 $p_a,p_b$,钦定 $a \lt b$,开始分类讨论:
- 若 $a, b \lt i$ 或 $a, b \gt i$:
- 显然不会影响到 $\tau$,因此 $\Delta = 0$。
- 若 $p_a, p_b \lt p_i$ 或 $p_a, p_b \gt p_i$:
- 此时 $a,b$ 分居两边,但同高同低。
- 也不会影响到 $\tau$,因此 $\Delta = 0$。
- 若 $p_a \lt p_i \lt p_b$:
- 并不难发现 $\tau$ 会被影响,$p_i$ 前面多一个比它大的,后面多一个比它小的。
- 因此 $\Delta = 2$。
- 若 $p_a \gt p_i \gt p_b$:
- 和上一个同理,$\Delta = -2$。
因此关于 $i$ 的逆序对奇偶性不会变化。
问题是知道这玩意儿没啥用,我们真正要求的是整个排列的奇偶性。
但这东西启发我们构造一种特殊排列,满足 $p_i=j$ 的同时能快速计算出逆序对奇偶性。
- 若 $i=j$:
- 这没什么好说的,直接构造 $\{1,2,\dots,i,\dots,n\}$,逆序对为 $0$。
- 若 $i \lt j$:
- 构造排列 $\{1,2,\dots,j,i,i+1,i+2,\dots,j-1,j+1,\dots,n\}$。
- 此时 $[i+1,j]$ 区间的数都比 $p_i$ 小,而如果去掉 $p_i$,整体是单增的。
- 因此逆序对数为 $j-i$。
- 若 $i \gt j$:
- 和之前同理,可以得到逆序对数为 $i-j$。
得出结论:逆序对数为 $|i-j|$。
我们只关注奇偶性,所以 $|i-j| \equiv i+j \pmod 2$。
则有:
$$|A| = \sum_{p} (-1)^{\tau(p)} \prod_{k=1}^{n} a_{k,p_k}$$
$$=\sum\limits_{j=1}^{n} a_{i,j} \times (-1)^{i+j} \sum_{p} (-1)^{\tau(p)} \prod_{k=1,k \not= i}^{n} a_{k,p_k}$$
$$=\sum\limits_{j=1}^{n} a_{i,j} \times C_{i,j}$$
行列式的求值
一个简单的方法是:直接按照定义式求值,需要枚举全排列,再枚举每一位,所以复杂度为 $O(n! \times n)$。
另一种方法类似于高斯消元,先把矩阵转为上三角,再转化为对角矩阵,在消元的过程中根据行列式的性质顺便更新行列式的值。
显然对角矩阵的行列式的值就是对角线乘积。
消成上三角矩阵
利用性质 1、2、6,可以很容易地用高斯消元求解行列式的值,由于取模操作,套一个逆元就行了。
这对吗?这不对!模数不一定为质数,所以逆元可能不存在。
考虑求 $\gcd$ 的过程:辗转相除法。它最后递归的终止条件是 $b=0$,此时返回 $a$。
这正是我们所需要的:把一个位置消成 $0$,保留另一个位置。
与之不同的,我们现在是把两行的元素同时进行辗转相除。
消成对角矩阵
在思考如何消成对角矩阵之前先冷静分析一下。
现在对于 $a_{i,j},i \lt j$ 的位置显然都已经是 $0$ 了,因此只要有 $p_i = j, i \lt j$ 那么整个式子乘上 $0$ 结果就是 $0$。
那么就相当于限制 $p_i \leq i$,但是排列又不能重复填数,所以一定在 $p_i = i$ 的时候才有值。
因此只有对角线信息有用。
那么根本不需要消成对角矩阵,直接计算即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 605;
int n, mod, a[N][N];
long long ans;
inline void exchange(int x, int y) { for (int i = 1; i <= n; i++) swap(a[x][i], a[y][i]); ans *= -1; }
int main() {
scanf("%d%d", &n, &mod), ans = 1ll;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++) {
while (a[i][i]) {
int t = a[j][i] / a[i][i];
for (int k = i; k <= n; k++) a[j][k] = ( (a[j][k] - a[i][k] * 1ll * t) % mod + mod) % mod;
exchange(i, j);
}
exchange(i, j);
}
ans = (ans + mod) % mod;
for (int i = 1; i <= n; i++) ans = (ans * 1ll * a[i][i]) % mod;
printf("%lld\n", ans);
return 0;
}
矩阵树定理
矩阵树定理一般被用来求解无权图生成树数量,通过构造矩阵把图论问题转化为行列式求值问题。
构造矩阵
度数矩阵 $D$
- 对于无向图:定义 $D_{i,i} = deg_i$,即度数;对于 $i \not= j$ 定义 $D_{i,j} = 0$。
- 对于有向图:定义 $D^{in} = in_i$,即入度;$D^{out} = out_i$,即出度;对于 $i \not= j$ 则 $D^{in}_{i,j} = D^{out}_{i,j} = 0$。
邻接矩阵 $A$
定义 $A_{i,j}$ 表示图中 $i,j$ 两点间有多少条边。
Laplace 矩阵 $L$(又名 Kirchhoff 矩阵)
- 对于无向图:定义 $L_{i,j} = D_{i,j} - A_{i,j}$。
- 对于有向图:定义 $L^{in} = D^{in}_{i,j} - A_{i,j}$,即入度 $L$ 矩阵;$L^{out} = D^{out}_{i,j} - A_{i,j}$,即出度 $L$ 矩阵。
子矩阵 $R$
为下文行文方便,令 $R(M,i)$ 表示把 $M$ 矩阵删除第 $i$ 行第 $i$ 列得到的矩阵。
定理
无向图
- 对于无向图:
- 它的生成树数量为:$|R(L, rt)|$。
- 其中 $rt$ 为任意点。
- 对于有向图:
- 它的叶向生成树数量为:$|R(L^{in}, rt)|$。
- 它的根向生成树数量为:$|R(L^{out}, rt)|$。
- 其中 $rt$ 表示树根。
证明略。
模板题
题目定义一个生成树的权值为边权乘积。
矩阵树定理并不能处理带权问题,但是可以转化:把一条边权为 $w$ 的边转化为 $w$ 条无权边。
然后就可以套用矩阵树定理了。
本题要求以一为根的叶向树,所以直接把点编号 $-1$ 即可,做行列式求值的时候求 $[1,n-1]$。
#include <bits/stdc++.h>
using namespace std;
const int N = 605, mod = 1e9 + 7;
int n, m, T;
long long a[N][N], ans;
inline void exchange(int x, int y) { for (int i = 1; i <= n; i++) swap(a[x][i], a[y][i]); ans *= -1; }
void solve() {
ans = 1ll;
for (int i = 1; i < n; i++)
for (int j = i + 1; j < n; j++) {
while (a[i][i]) {
int t = a[j][i] / a[i][i];
for (int k = i; k < n; k++) a[j][k] = ( (a[j][k] - a[i][k] * 1ll * t) % mod + mod) % mod;
exchange(i, j);
}
exchange(i, j);
}
ans = (ans + mod) % mod;
for (int i = 1; i < n; i++) ans = (ans * 1ll * a[i][i]) % mod;
printf("%lld\n", ans);
}
int main() {
scanf("%d%d%d", &n, &m, &T);
for (int i = 1, u, v, w; i <= m; i++) {
scanf("%d%d%d", &u, &v, &w), u--, v--;
if (!T) a[u][u] += w, a[v][v] += w, a[u][v] -= w, a[v][u] -= w;
else a[v][v] += w, a[u][v] -= w; // 叶向树,用 in
}
for (int i = 1; i < n; i++)
for (int j = 1; j < n; j++) a[i][j] = (a[i][j] % mod + mod) % mod;
solve();
return 0;
}
四、矩阵求逆
对于 $A$ 求一个矩阵,使得它和 $A$ 做矩阵乘法得到单位矩阵。
在 $A$ 的右边拼一个单位矩阵,然后对左边的 $A$ 做高斯消元,使得它变成单位矩阵。
此时右边的单位矩阵就变成了逆矩阵。
#include <bits/stdc++.h>
using namespace std;
const int N = 405, mod = 1e9 + 7;
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * 1ll * a % mod;
a = a * 1ll * a % mod, k >>= 1;
}
return res;
}
int n, m, a[N][N << 1];
void solve() {
for (int i = 1; i <= n; i++) {
int t = i;
for (int j = i + 1; j <= n; j++)
if (a[t][i] < a[j][i]) t = j;
for (int j = 1; j <= m; j++) swap(a[t][j], a[i][j]);
if (a[i][i] == 0) puts("No Solution"), exit(0);
int inv = qmi(a[i][i], mod - 2);
for (int j = i; j <= m; j++) a[i][j] = (a[i][j] * 1ll * inv) % mod;
for (int j = i + 1; j <= n; j++) {
int p = a[j][i];
for (int k = i; k <= m; k++) a[j][k] = (a[j][k] - (p * 1ll * a[i][k] % mod) + mod) % mod;
}
}
for (int i = n; i >= 1; i--)
for (int j = i + 1; j <= n; j++) {
int p = a[i][j];
for (int k = i + 1; k <= m; k++) a[i][k] = (a[i][k] - (p * 1ll * a[j][k] % mod) + mod) % mod;
}
for (int i = 1; i <= n; i++, puts(""))
for (int j = 1; j <= n; j++) printf("%d ", a[i][j + n]);
}
int main() {
scanf("%d", &n), m = n << 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++) a[i][i + n] = 1;
solve();
return 0;
}
来点 [CQOI2018] 社交网络 「NOI2007」 生成树计数 「联合省选 2020 A」作业题
收到 :)
但我得先补概率与期望和明天的专题,太多了补不完 T_T 老师要查作业
讲了 Matrix-tree 为啥不讲点题