Algorithm Review 9 数学相关

概率与期望

  • 结论1 x x x 为离散随机变量,且 x ∈ N x\in\mathbb N xN,则 E ( x ) = ∑ i = 1 ∞ i ⋅ P ( x = i ) = ∑ i = 1 ∞ P ( x ≥ i ) E(x) = \sum \limits_{i = 1}^{\infty}i·P(x = i) = \sum \limits_{i = 1}^{\infty}P(x \ge i) E(x)=i=1iP(x=i)=i=1P(xi)

树上随机游走

  • 给定一棵树,从树中的某点 x x x 出发,设其子树大小为 size x \text{size}_x sizex,结点的度数为 deg x \text{deg}_x degx,每次等概率地选择一条相邻的边游走, w ( u , v ) \text{w}(u,v) w(u,v) 表示 u u u v v v 的边权(距离), son x \text{son}_x sonx 表示其子结点集合, sibling x \text{sibling}_x siblingx 表示其兄弟结点集合。
  • 设其第一次游走到其父结点 fa x \text{fa}_x fax 的期望距离为 f x f_x fx,不难得到:
    f x = w ( x , fa x ) + ∑ y ∈ son x ( w ( x , y ) + f y + f x ) deg x f_x = \frac{\text{w}(x,\text{fa}_x) + \sum \limits_{y \in \text{son}_x}(\text{w}(x,y)+f_y+f_x)}{\text{deg}_x} fx=degxw(x,fax)+ysonx(w(x,y)+fy+fx)
  • 移项得:
    f x = w ( x , fa x ) + ∑ y ∈ son x w ( x , y ) + ∑ y ∈ son x f y f_x = \text{w}(x,\text{fa}_x) + \sum \limits_{y \in \text{son}_x}\text{w}(x,y) + \sum \limits_{y \in \text{son}_x}f_y fx=w(x,fax)+ysonxw(x,y)+ysonxfy
  • 特别地,若 w \text{w} w 恒为 1, f x = 2 size x − 1 f_x = 2\text{size}_x - 1 fx=2sizex1
  • 类似地,设 g x g_x gx 表示从 fa x \text{fa}_x fax 走到 x x x 的期望距离,不难得到:
    g x = w ( x , fa x ) + ( w ( fa x , fa fa x ) + g fa x + g x ) + ∑ y ∈ sibling x ( w ( fa x , y ) + f y + g x ) deg fa x g_x = \dfrac{\text{w}(x,\text{fa}_x) + (\text{w}(\text{fa}_x, \text{fa}_{\text{fa}_x}) + g_{\text{fa}_x}+g_x)+\sum\limits_{y\in\text{sibling}_x}({\text{w}(\text{fa}_x,y)+f_y + g_x})}{\text{deg}_{\text{fa}_x}} gx=degfaxw(x,fax)+(w(fax,fafax)+gfax+gx)+ysiblingx(w(fax,y)+fy+gx)
  • 化简得 g x = g fa x + f fa x − f x g_x = g_{\text{fa}_x} + f_{\text{fa}_x} - f_x gx=gfax+ffaxfx

min-max容斥

  • 常见结论如下:

max ⁡ i ∈ S x i = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − 1 min ⁡ i ∈ T x i min ⁡ i ∈ S x i = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − 1 max ⁡ i ∈ T x i E ( max ⁡ i ∈ S x i ) = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − 1 E ( min ⁡ i ∈ T x i ) E ( min ⁡ i ∈ S x i ) = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − 1 E ( max ⁡ i ∈ T x i ) kth max ⁡ i ∈ S x i = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − k ( ∣ T ∣ − 1 k − 1 ) min ⁡ i ∈ T x i kth min ⁡ i ∈ S x i = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − k ( ∣ T ∣ − 1 k − 1 ) max ⁡ i ∈ T x i E ( kth max ⁡ i ∈ S x i ) = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − k ( ∣ T ∣ − 1 k − 1 ) E ( min ⁡ i ∈ T x i ) E ( kth min ⁡ i ∈ S x i ) = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − k ( ∣ T ∣ − 1 k − 1 ) E ( max ⁡ i ∈ T x i ) lcm i ∈ S x i = ∏ T ⊆ S ( gcd ⁡ i ∈ T x i ) ( − 1 ) ∣ T ∣ − 1 \max\limits_{i \in S}x_i = \sum \limits_{T\subseteq S}(-1)^{|T| - 1}\min\limits_{i \in T}x_i \\\min\limits_{i \in S}x_i = \sum \limits_{T\subseteq S}(-1)^{|T| - 1}\max\limits_{i \in T}x_i \\E(\max\limits_{i \in S}x_i) = \sum \limits_{T\subseteq S}(-1)^{|T| - 1}E(\min\limits_{i \in T}x_i) \\E(\min\limits_{i \in S}x_i) = \sum \limits_{T\subseteq S}(-1)^{|T| - 1}E(\max\limits_{i \in T}x_i) \\\text{kth}\max\limits_{i \in S}x_i = \sum\limits_{T \subseteq S}(-1)^{|T|-k}\binom{|T| - 1}{k - 1}\min\limits_{i \in T}x_i\\\text{kth}\min\limits_{i \in S}x_i = \sum\limits_{T \subseteq S}(-1)^{|T|-k}\binom{|T| - 1}{k - 1}\max\limits_{i \in T}x_i\\E(\text{kth}\max\limits_{i \in S}x_i) = \sum\limits_{T \subseteq S}(-1)^{|T|-k}\binom{|T| - 1}{k - 1}E(\min\limits_{i \in T}x_i)\\E(\text{kth}\min\limits_{i \in S}x_i) = \sum\limits_{T \subseteq S}(-1)^{|T|-k}\binom{|T| - 1}{k - 1}E(\max\limits_{i \in T}x_i)\\\underset{i\in S}{\text{lcm}} x_i = \prod\limits_{T \subseteq S}\left(\gcd\limits_{i \in T}x_i\right)^{(-1)^{|T| - 1}} iSmaxxi=TS(1)T1iTminxiiSminxi=TS(1)T1iTmaxxiE(iSmaxxi)=TS(1)T1E(iTminxi)E(iSminxi)=TS(1)T1E(iTmaxxi)kthiSmaxxi=TS(1)Tk(k1T1)iTminxikthiSminxi=TS(1)Tk(k1T1)iTmaxxiE(kthiSmaxxi)=TS(1)Tk(k1T1)E(iTminxi)E(kthiSminxi)=TS(1)Tk(k1T1)E(iTmaxxi)iSlcmxi=TS(iTgcdxi)(1)T1

  • 证明待补充。

博弈论

  • 待补充。

Nim Game

  • 给定 n n n 堆石子,第 i i i 堆石子有 a i a_i ai 个,两名玩家轮流行动,每次可以任选一堆,取走任意多个石子,但不能不取,取走最后一个石子者获胜。
  • 结论1 两人均采用最优策略,若 ⨁ i = 1 n a i = 0 \bigoplus\limits_{i = 1}^{n}a_i = 0 i=1nai=0 则先手必败,否则先手必胜。

证明 显然末态(全 0 局面)满足先手必败条件,我们只需证明对于任意 ⨁ i = 1 n a i ≠ 0 \bigoplus\limits_{i = 1}^{n}a_i \not= 0 i=1nai=0 的局面,一定存在一种操作使其能转变为 ⨁ i = 1 n a i = 0 \bigoplus \limits_{i = 1}^{n}a_i = 0 i=1nai=0 的局面。

设此时 ⨁ i = 1 n a i = k ≠ 0 \bigoplus \limits_{i = 1}^{n}a_i = k \not= 0 i=1nai=k=0 k k k 在二进制下最高位的 1 在第 x x x 位,则一定有奇数个 a i a_i ai 在二进制下第 x x x 位为 1。找到其中一个 a i a_i ai,则 a i ⊕ k < a i a_i \oplus k < a_i aik<ai, 取 a i − ( a i ⊕ k ) a_i - (a_i \oplus k) ai(aik) 个石子,则局面转变为 ⨁ i = 1 n a i = 0 \bigoplus \limits_{i = 1}^{n} a_i = 0 i=1nai=0

  • 结论2 先手必败的玩家总能使接下来的操作每次只取一个石子。

证明 先手必败的玩家在 lowbit ( a i ) \text{lowbit}(a_i) lowbit(ai) 最小的石子堆中取走恰好一个,则先手必胜的玩家也必须在另一堆 lowbit ( a i ) \text{lowbit}(a_i) lowbit(ai) 最小的石子堆中取走恰好一个。

Bash Game

  • 给定一堆石子,石子总个数为 n n n,两名玩家轮流行动,每次至少取 1 个,至多取 m m m 个,取走最后一个石子者获胜。
  • 结论 两人均采用最优策略,若 ( m + 1 ) ∣ n (m + 1) | n (m+1)n 则先手必败,否则先手必胜。

证明 显然末态(0)满足先手必败条件,对于任意 ( m + 1 ) ∤ n (m + 1)\not| n (m+1)n 的局面,先手取 n n n m + 1 m + 1 m+1 的余数个石子,一定能使其转变为 ( m + 1 ) ∣ n (m + 1)|n (m+1)n 的局面。

Wythoff Game

  • 给定两堆石子,石子数分别为 a , b ( a < b ) a,b(aa,b(a<b),两名玩家轮流行动,每次可以在一堆中取石子,或从两堆中取走相同个数的石子,数量不限,取走最后一个石子者获胜。
  • 结论 两人均采用最优策略,若满足 a = ⌊ 5 + 1 2 k ⌋ , b = a + k , k ∈ N a = \lfloor \frac{\sqrt 5 + 1}{2} k \rfloor, b = a+k,k\in\mathbb N a=25 +1k,b=a+k,kN 则先手必败,否则先手必胜。

证明 待补充。

Fibonacci Game

  • 给定一堆石子,石子总个数为 n n n,两名玩家轮流行动:

    • 先手不能一次取完所有石子,至少取 1 个。
    • 之后每次取的石子数至少为 1,至多为对手取的石子数的 2 倍。
  • 取走最后一个石子者获胜。

  • 结论 两人均采用最优策略,若 n n n Fibonacci \text{Fibonacci} Fibonacci 数则先手必败,否则先手必胜。

证明 待补充。

SG 函数

  • 在有向图游戏中,规定对于所有出度为 0 的点 x x x S G ( x ) = 0 SG(x) = 0 SG(x)=0
  • 对于其余的点 x x x,设其后继结点分别为 y 1 , y 2 , … , y k y_1,y_2,\dots,y_k y1,y2,,yk,则 S G ( x ) = mex { S G ( y i ) } , 1 ≤ i ≤ k SG(x) = \text{mex}\{SG(y_i)\}, 1 \le i \le k SG(x)=mex{SG(yi)},1ik
  • S G ( x ) = 0 SG(x) = 0 SG(x)=0 为必败态, S G ( x ) ≠ 0 SG(x)\not = 0 SG(x)=0 为必胜态。
  • 定义相互独立的有向图游戏 G 1 , G 2 , … , G m G_1, G_2, \dots, G_m G1,G2,,Gm 的和 G G G
    • 在游戏进行的过程中,游戏者可以任意挑选其中的一个游戏进行决策,最终无法决策者判负。
    • S G ( G ) = ⨁ i = 1 m S G ( G i ) SG(G) = \bigoplus \limits_{i = 1}^{m}SG(G_i) SG(G)=i=1mSG(Gi),证明类似 Nim Game
  • 许多博弈题目的 S G ( x ) SG(x) SG(x) 有特殊规律,可以打表得出。

二分图博弈

  • 给定一个二分图和起始点,两名玩家轮流行动,每次选择移动到一个与当前点相邻且未经过的点,不能选择点的玩家算输。
  • 结论 两人均采用最优策略,若最大匹配一定包含起始点,则先手必胜,否则先手必败。

证明 若最大匹配一定包含起始点,则先手始终可以沿着匹配边移动,后手不可能选到非匹配点。若后手选到一个非匹配点 P n P_n Pn,设路径为 P 1 → P 2 → ⋯ → P n ( n 为奇数 ) P_1 \to P_2 \to \dots \to P_n(n为奇数) P1P2Pn(n为奇数),则现有匹配为 { P 1 − P 2 , … , P n − 2 − P n − 1 } \{P_1-P_2,\dots, P_{n-2}-P_{n-1}\} {P1P2,,Pn2Pn1},则可得到另一个最大匹配 { P 2 − P 3 , … , P n − 1 − P n } \{P_2-P_3, \dots, P_{n - 1}-P_n\} {P2P3,,Pn1Pn},与最大匹配一定包含起始点矛盾。

若最大匹配不一定包含起始点,取其中一个不包含起始点的最大匹配 M M M,先手每次移动一定会移动到某个匹配点上,否则图中存在增广路,与最大匹配矛盾,后手只需沿匹配边移动即可。

删边博弈

  • 以下内容摘自《组合游戏略述——浅谈SG游戏的若干拓展及变形》
    Algorithm Review 9 数学相关_第1张图片
    Algorithm Review 9 数学相关_第2张图片
    Algorithm Review 9 数学相关_第3张图片
    Algorithm Review 9 数学相关_第4张图片

线性代数

高斯消元

解实数方程组

  • 模拟手动消元过程即可。
inline bool Gauss()
{ 
    for (int i = 1; i <= n; ++i)
    {
        int l = i;
        for (int j = i + 1; j <= n; ++j) 
			if (fabs(f[l][i]) < fabs(f[j][i])) 
				l = j;
        if (l != i)
       		for (int j = 1; j <= n + 1; ++j) 
				std::swap(f[l][j], f[i][j]);
        if (fabs(f[i][i]) <= eps) 
			return false;
		ld tmp = f[i][i];
		for (int j = 1; j <= n + 1; ++j)
			f[i][j] /= tmp;
        for (int j = 1; j <= n; ++j)
			if (j != i)
			{
				ld tmp = f[j][i];
			   	for (int k = i; k <= n + 1; ++k)
					f[j][k] -= f[i][k] * tmp;
            }
    }
    return true;
}

解异或方程组

  • 大致流程类似,可用 bitset 优化。
  • 因为系数只有 0/1,题目常常要求计算自由元个数,进而能求出解的组数,注意实现的细节。
bitset<N> f[N];

inline int Guass() //返回自由元个数
{
    // n 为未知量个数,m 为方程数
    int ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        int pos = 0;
        for (int j = 1; j <= m; ++j)
            if (!vis[j] && f[j][i])
            {
                pos = j;
                break;
            }
        if (pos)
        {
            if (pos != i)
                swap(f[pos], f[i]);
            vis[i] = true;
            for (int j = 1; j <= m; ++j)
                if (!vis[j] && f[j][i]) f[j] ^= f[i];
        }
        else 
            ++ans; //统计自由元个数
    }
	
	for (int i = 1; i <= m; ++i) 
	{
		bool flag = false;
		for (int j = 1; j <= n; ++j)
			if (f[i][j])
			{
				flag = true;
				break;
			}
		if (!flag && f[i][n + 1]) //无解情况
			return -1;
	}
    return ans;
}

求行列式

  • 消成上三角矩阵直接计算即可。
  • 若模数为质数,可以直接求逆元,否则消元的过程可通过辗转相除法实现。
  • f i , i > 0 f_{i,i} > 0 fi,i>0 时每次迭代至少减少一半,实际的时间复杂度为 O ( n 2 log ⁡ w + n 3 ) \mathcal O(n^2\log w +n ^3) O(n2logw+n3),同样的原理也可以用于分析区间 gcd \text{gcd} gcd,实际上合并线段树上 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 个区间的答案总迭代次数也是 O ( log ⁡ w ) \mathcal O(\log w) O(logw)
inline int Det()
{
	int res = 1, opt = 0;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = i + 1; j <= n; ++j)
		{
			while (f[i][i])
			{
				int tmp = f[j][i] / f[i][i];
				for (int k = i; k <= n; ++k)
					dec(f[j][k], 1ll * tmp * f[i][k] % mod);
				std::swap(f[i], f[j]); 
				opt ^= 1;
			}
			std::swap(f[i], f[j]);
			opt ^= 1;
		}
		res = 1ll * res * f[i][i] % mod;
		if (!res)
			return 0;
	}
	return opt ? mod - res : res;
}

线性基

  • 线性基是一种特殊的基,它具有如下性质:
    • 原集合任意非空子集得到的异或和都能由线性基内某个唯一的非空子集异或得到。
    • 线性基是满足上述性质最小的集合,且没有异或和为 0 的子集。
    • 线性基中每个元素的二进制最高位互不相同。
  • 由性质 1 和性质 2,设线性基内元素个数为 c n t cnt cnt,则该线性基子集的异或和种数为 2 c n t 2^{cnt} 2cnt若插入线性基的元素个数为 n n n,(可为空)子集异或和为 0 的方案数为 2 n − c n t 2^{n - cnt} 2ncnt,非空子集异或和为 0 的方案数为 2 n − c n t − 1 2^{n - cnt} - 1 2ncnt1
  • 由性质 2,若需要判断原集合能否找出一个非空子集异或和为 0,构造其线性基时需要特殊记录(代码中的 flag)。

基础操作

  • 只作简要叙述,具体实现见代码。

压位

  • 若线性基表示的数字较大,可通过重载 bitset 进行压位优化。
struct bigint
{
	bitset<N> x;
	
	inline void Clear() {x.reset();}
	
	inline void scan(char *s, int n)
	{
		x.reset();
		std::reverse(s, s + n);
		for (int i = 0; i < n; ++i)
			if (s[i] == '1')
				x[N - 1 - i] = 1;
	}
	
	inline bool operator < (const bigint &a) const
	{
		int p = (x ^ a.x)._Find_first();
		if (p == N)
			return false;
		return !x[p];
	}
	
	inline bool operator <= (const bigint &a) const 
	{
		int p = (x ^ a.x)._Find_first();
		return p == N || !x[p];
	}
}

异或上指定数后的最值

  • 从高位到低位贪心即可。

子集异或和第 k 小数

  • 我们先对构建出的线性基做一次 reBuild 操作,具体来说,从低位到高位,尽量用低位的元素消去高位元素上的 1,只保留线性基中非 0 的元素。
  • 这样显然不会破坏线性基的性质,同时我们能保证对于线性基内任意一个子集的异或和以及任意一个不在该子集内的元素,异或上该元素都能使该子集的异或和增大。
  • 之后从高位到低位贪心即可。

求有多少个子集异或和小于等于 r

  • 同样先做一次 reBuild 操作,只保留线性基中非 0 的元素。
  • 初始答案为 1( r ≥ 0 r \ge 0 r0,而空集一定能够取到 0),从高位到低位贪心,若异或上当前元素仍小于等于 r r r,则异或上当前元素,将答案加上 2 线性基中比该数小的非 0 元素个数 2^{线性基中比该数小的非 0 元素个数} 2线性基中比该数小的非0元素个数
  • 设线性基内元素个数为 c n t cnt cnt,原集合的元素个数为 n n n,若要求有多少个原集合的子集异或和小于等于 r r r,将上述询问的答案乘上 2 n − c n t 2^{n-cnt} 2ncnt 即可。

两个线性基的并

  • 暴力将其中一个线性基的每个元素插入另一个线性基即可。

两个线性基的交

  • 给定两个线性基 a , b a,b a,b,求出一个线性基 c c c,使得其任意子集的异或和在 a , b a,b a,b 中均能由子集异或和表示。
  • 记线性基 a , b a,b a,b 中元素分别为 a 0 , a 1 , … , a L a_0, a_1, \dots, a_L a0,a1,,aL b 0 , b 1 , … , b L b_0, b_1, \dots, b_L b0,b1,,bL,对于 c c c 中某一子集的异或和
    x = ⨁ i = 1 n a p i = ⨁ i = 1 m b q i x = \bigoplus\limits_{i = 1}^{n}a_{p_i} = \bigoplus \limits_{i = 1}^{m}b_{q_i} x=i=1napi=i=1mbqi
  • 其中 0 ≤ p 1 < p 2 < ⋯ < p n ≤ L , 0 ≤ q 1 < q 2 < ⋯ < q m ≤ L 0 \le p_1 < p_2 < \dots < p_n \le L, 0 \le q_1 < q_2 < \dots < q_m \le L 0p1<p2<<pnL,0q1<q2<<qmL
  • 考虑枚举从高位到低位枚举 b q 1 b_{q_1} bq1,用 r e s res res 维护 a a a b q 1 + 1 , b q 1 + 2 , … , b L b_{q_1+1}, b_{q_1 + 2}, \dots, b_L bq1+1,bq1+2,,bL 组成的线性基的并,同时维护 r e s res res 中每个元素包含了 a a a 中的哪些元素。
  • 当插入 b q 1 b_{q_1} bq1 时,若 b q 1 b_{q_1} bq1 能由 r e s res res 的某个子集异或和表示,则我们通过维护的信息可唯一确定 x x x,将其插入 c c c 即可。
const int L = 60; 
ll cur[L + 1];
int cm;
 
struct lib
{
	ll g[L + 1];
	bool flag0;
	
	inline void Clear()
	{
		flag0 = false;
		for (int i = 0; i <= L; ++i)
			g[i] = 0;	
	}
	
	inline bool Insert(ll v)
	{
		for (int i = L; i >= 0; --i)
			if (v >> i & 1)
			{
				if (g[i])
					v ^= g[i];
				else
				{
					g[i] = v;
					return true;
				}		
			} 
		flag0 = true;
		return false;
	}
	
    inline int queryCnt()
    {
    	int cnt = 0;
        for (int i = 0; i <= L; ++i)
            cnt += g[i] > 0;
       	return cnt;
	}
    
	inline ll queryMax(ll res = 0)
	{
		for (int i = L; i >= 0; --i)
			if ((res ^ g[i]) > res)
				res ^= g[i];
		return res;
	}
	
	inline ll queryMin(ll res = -1)
	{ // res = -1 时表示询问线性基内子集异或和的最小值
		if (res == -1)
		{
			if (flag0)
				return 0;
			for (int i = 0; i <= L; ++i)
				if (g[i])
					return g[i];
		}
		else
		{
			for (int i = L; i >= 0; --i)
				if (res >> i & 1)
					res ^= g[i];
			return res;
		}
	}
	
	inline void reBuild()
	{ // 使用 queryKth() 之前记得调用
		cm = 0;
		for (int i = 0; i <= L; ++i)
			if (g[i])
			{
				for (int j = 0; j < i; ++j)
					if (g[i] >> j & 1)
						g[i] ^= g[j];
				cur[cm++] = g[i];
			}
	}
	
	inline ll queryKth(ll k)
	{
		if (flag0)
			--k;
		if (!k)
			return 0;
		if (k >= (1ll << cm))
			return -1;
		ll res = 0;
		for (int i = 0; i < cm; ++i)
			if (k >> i & 1)
				res ^= cur[i];
		return res;
	}

	inline ll queryRank(ll r)
	{
    	ll res = 0, cnt = 1;
    	for (int i = cm - 1; i >= 0; --i)
    	{
			ll temp = res ^ cur[i];
        	if (temp <= r)
        	{
            	res = temp;
                cnt += 1ll << i;
            }
    	}	
    	return cnt;
	}

	friend inline lib operator | (const lib &a, const lib &b)
	{
		lib c = a;
		for (int i = L; i >= 0; --i)
			if (b.g[i])
				c.Insert(b.g[i]);
		c.flag0 |= b.flag0;
		return c;
	}
	
	friend inline lib operator & (const lib &a, const lib &b)
	{
		lib c, d, res;
		c.flag0 = a.flag0 && b.flag0;
		for (int i = L; i >= 0; --i)
		{
			res.g[i] = a.g[i];
			d.g[i] = 1ll << i;
		}
		for (int i = L; i >= 0; --i)
			if (b.g[i])
			{
				ll v = b.g[i], k = 0;
				bool flag = true;
				for (int j = L; j >= 0; --j)
					if (v >> j & 1)
					{
						if (res.g[j])
						{
							v ^= res.g[j];
							k ^= d.g[j];
						}
						else
						{
							flag = false;
							res.g[j] = v;
							d.g[j] = k;
							break ;
						}
					}
				if (flag)
				{
					v = 0;
					for (int j = L; j >= 0; --j)
						if (k >> j & 1)
							v ^= a.g[j];
					c.Insert(v);
				}
			}
		return c;
	}
};

常见应用

可删除的线性基

  • 强行实现可删除的线性基需要记录原集合的信息,较为麻烦且复杂度不科学,这里略去。
  • 线性基的撤回操作实现较为容易,与线段树分治结合即可实现离线的可删除。

区间线性基

  • 给定序列 a a a,多次询问 [ l , r ] [l, r] [l,r] 的元素构成的线性基。
    • 显然可以得到线段树或 ST 表的做法。
    • 实际上还有更简便的做法,设 s i s_{i} si 表示 [ 1 , i ] [1, i] [1,i] 的元素构成的线性基,同时记录 p i , j p_{i,j} pi,j 表示 s i s_i si 中第 j j j 位的元素由 a a a 中哪个元素构成。构建 s i s_i si 时先令 s i = s i − 1 , p i , j = p i − 1 , j s_i = s_{i - 1}, p_{i,j} = p_{i - 1,j} si=si1,pi,j=pi1,j,若 a i a_i ai 能由 s i s_i si 中的元素表示,则删除表示 a i a_i ai 的元素中 p i , j p_{i, j} pi,j 最小的,并由 a i a_i ai 顶替。
    • 询问时只需取出 s i s_i si p i , j ≥ l p_{i,j}\ge l pi,jl 的元素构建线性基即可。
inline bool Insert(int t, ll v)
{
	s[t] = s[t - 1];
	p[t] = p[t - 1];
	ll tmp = t;
	for (int i = L; i >= 0; --i)
		if (v >> i & 1)
		{
			if (p[t].g[i] < tmp)
			{
				std::swap(s[t].g[i], v);
				std::swap(p[t].g[i], tmp);
			}
			v ^= s[t].g[i];			
		}
		else
		{
			s[t].g[i] = v;
			p[t].g[i] = tmp;
			return true;
		}
	s[t].flag0 = true;
	return false;
}
  • 给定序列 a a a,多次操作,询问 [ l , r ] [l, r] [l,r] 的元素构成的线性基或区间将 [ l , r ] [l,r] [l,r] 异或上一个数。
    • b i = a i ⊕ a i − 1 b_i = a_i \oplus a_{i - 1} bi=aiai1,用线段树维护序列 b b b 的区间线性基,则区间操作可转化为单点修改,同时还需一棵线段树维护序列 a a a,支持单点查询。
  • 给定一棵带点权的树,多次询问两点 ( x , y ) (x, y) (x,y) 路径上点权构成的线性基。
    • 倍增求出 x , y x,y x,y LCA \text{LCA} LCA,设为 z z z,设 a n c x , i anc_{x,i} ancx,i 表示 x x x 往上跳 2 i 2^i 2i 步得到的结点,预处理 f x , i f_{x,i} fx,i 表示 a n c x , i → x anc_{x,i}\to x ancx,ix 这条路径上(不包括 a n c x , i anc_{x,i} ancx,i)的点权构成的线性基。
    • 运用 ST 表 的思想,将 z → x z \to x zx z → y z \to y zy 两条路径分别处理,将每条路径的线性基拆成两个可重叠且长度为 2 的整数次幂的路径的线性基的并。
    • 设点数为 n n n,询问数为 q q q,可做到时间复杂度 O ( n log ⁡ n log ⁡ 2 w + q log ⁡ 2 w + q log ⁡ n ) \mathcal O(n\log n \log^2 w + q\log^2w + q\log n) O(nlognlog2w+qlog2w+qlogn)

连通块内任意两点的异或最短路

  • 任取连通块中的一棵生成树,对于任意一条非树边,取其与树边构成的环的异或和,则任意两点的异或最短路为它们在生成树上路径的异或和与若干个环的异或和异或的最小值,用线性基维护所有环的异或和即可。
  • 若要动态加边和删边可与线段树分治、并查集结合,在任意两点间连边可根据异或的性质转换成在它们并查集祖先间连边。

二阶常系数线性齐次递推式的通项

  • 给出递推式 f n = a f n − 1 + b f n − 2 f_n = a f_{n - 1} + b f_{n - 2} fn=afn1+bfn2,已知 f 0 , f 1 f_0, f_1 f0,f1,求 f n f_n fn 的通项公式。

  • 结论 x 1 , x 2 x_1, x_2 x1,x2 为方程 x 2 − a x − b = 0 x^2 - ax - b = 0 x2axb=0 的两根(可以为复数)。

    • x 1 ≠ x 2 x_1 \neq x_2 x1=x2 f n = A x 1 n + B x 2 n f_n = Ax_1^n + Bx_2^n fn=Ax1n+Bx2n
    • x 1 = x 2 x_1 = x_2 x1=x2 f n = ( A + B n ) x 1 n f_n = (A + Bn)x_1^n fn=(A+Bn)x1n
    • 其中 A , B A,B A,B 可以根据 f 0 , f 1 f_0, f_1 f0,f1 列方程组解出。

证明(实际可用基础的生成函数证明,以下是另一种证明方式)
尝试把递推式表示成等比数列的形式:
f n − x 1 f n − 1 = x 2 ( f n − 1 − x 1 f n − 2 ) f n = ( x 1 + x 2 ) f n − 1 − x 1 x 2 f n − 2 \begin{aligned}f_n - x_1 f_{n - 1} &= x_2(f_{n - 1} - x_1f_{n-2})\\f_n &= (x_1 + x_2) f_{n - 1} - x_1x_2 f_{n - 2} \\\end{aligned} fnx1fn1fn=x2(fn1x1fn2)=(x1+x2)fn1x1x2fn2
可以列出一个方程组:
{ x 1 + x 2 = a x 1 x 2 = − b \begin{cases}x_1 + x_2 = a\\x_1 x_2 = -b\\\end{cases} {x1+x2=ax1x2=b
由韦达定理得, x 1 , x 2 x_1,x_2 x1,x2 是方程 x 2 − a x − b = 0 x^2 - ax - b = 0 x2axb=0 的两根。

c = f 1 − x 1 f 0 c = f_1 - x_1f_0 c=f1x1f0,可列出 n n n 个方程:
{ f 1 − x 1 f 0 = c f 2 − x 1 f 1 = c x 2 f 3 − x 1 f 2 = c x 2 2 … f n − x 1 f n − 1 = c x 2 n − 1 \begin{cases}f_1 - x_1 f_0 &= c \\f_2 - x_1 f_1 &= c x_2 \\f_3 - x_1 f_2 &= c x_2^2 \\&…\\f_n - x_1 f_{n - 1} &= c x_2 ^{n - 1} \\\end{cases} f1x1f0f2x1f1f3x1f2fnx1fn1=c=cx2=cx22=cx2n1
将第 i i i 个方程乘上 x 1 n − i x_1^{n - i} x1ni,然后将所有方程相加,得到:
f n − x 1 n f 0 = c x 1 n − 1 ∑ i = 0 n − 1 ( x 2 x 1 ) i \begin{aligned}f_n - x_1^n f_0 = cx_1^{n - 1} \sum \limits_{i = 0}^{n - 1}\left(\frac{x_2}{x_1}\right)^{i}\\\end{aligned} fnx1nf0=cx1n1i=0n1(x1x2)i
x 1 = x 2 x_1 = x_2 x1=x2,此时 A = f 0 , B = c x 1 A = f_0, B = \frac{c}{x_1} A=f0,B=x1c
f n − x 1 n f 0 = c n x 1 n − 1 f n = c n x 1 n − 1 + x 1 n f 0 f n = ( f 0 + c x 1 n ) x 1 n \begin{aligned}f_n - x_1^n f_0 &= cnx_1^{n - 1}\\f_n &= cnx_1^{n - 1} + x_1^nf_0\\f_n &= \left(f_0 + \frac{c}{x_1} n \right) x_1^n \\\end{aligned} fnx1nf0fnfn=cnx1n1=cnx1n1+x1nf0=(f0+x1cn)x1n
x 1 ≠ x 2 x_1 \neq x_2 x1=x2,由等比数列求和, A = f 0 − c x 2 − x 1 , B = c x 2 − x 1 A = f_0 - \frac{c}{x_2 - x_1}, B = \frac{c}{x_2 - x_1} A=f0x2x1c,B=x2x1c
f n − x 1 n f 0 = c x 1 n − 1 ( x 2 x 1 ) n − 1 x 2 x 1 − 1 f n = c x 2 n − x 1 n x 2 − x 1 + x 1 n f 0 f n = ( f 0 − c x 2 − x 1 ) x 1 n + c x 2 − x 1 x 2 n \begin{aligned}f_n - x_1^n f_0 &= cx_1^{n - 1} \frac{\left( \frac{x_2}{x_1}\right)^{n} - 1}{\frac{x_2}{x_1} - 1}\\f_n &= c \frac{x_2^n - x_1^n}{x_2 - x_1} + x_1^n f_0 \\f_n &= \left(f_0 - \frac{c}{x_2 - x_1} \right)x_1^n + \frac{c}{x_2 - x_1}x_2^n\\\end{aligned} fnx1nf0fnfn=cx1n1x1x21(x1x2)n1=cx2x1x2nx1n+x1nf0=(f0x2x1c)x1n+x2x1cx2n

矩阵的 LU 分解

  • 定义 对于 n n n 阶矩阵 A A A,若存在一个下三角矩阵 L L L 和一个上三角矩阵 U U U,使得 A = L U A = LU A=LU,则称 A = L U A = LU A=LU A A A L U LU LU 分解。
  • 定理 n n n 阶矩阵 A A A 的前 n − 1 n - 1 n1 个顺序主子式 D k ≠ 0 ( 1 ≤ k ≤ n − 1 ) D_k \not = 0(1 \le k \le n - 1) Dk=0(1kn1),则 A A A 可以唯一地分解成一个单位下三角矩阵 L L L 和一个上三角矩阵 U U U

证明 先证明此时一定存在 A A A 的分解方案。

  • A k A_k Ak 表示 A A A k k k k k k 列构成的子矩阵,则 D k = det ( A k ) D_k = \text{det}(A_k) Dk=det(Ak)

  • 由行列式的定义可知,若我们每次通过第三种初等行变换将 A k ( 1 ≤ k ≤ n − 1 ) A_k(1\le k \le n - 1) Ak(1kn1) 化为上三角矩阵,则 A k A_k Ak 对角线上的元素一定不为 0。

  • 因此,通过第三种初等行变换我们一定能将 A A A 化为上三角矩阵 U U U,则有: P l P l − 1 … P 1 A = U ⇔ A = ( P l P l − 1 … P 1 ) − 1 U P_{l}P_{l - 1}\dots P_1 A = U \Leftrightarrow A = (P_{l} P_{l - 1} \dots P_1)^{-1}U PlPl1P1A=UA=(PlPl1P1)1U

  • L = ( P l P l − 1 … P 1 ) − 1 = P 1 − 1 … P l − 1 − 1 P l − 1 L = (P_lP_{l - 1}\dots P_1)^{-1} = P_1^{-1}\dots P_{l - 1}^{-1}P_l^{-1} L=(PlPl1P1)1=P11Pl11Pl1,由于第三种初等行变换对应的初等矩阵及其逆均为单位下三角矩阵,故 L L L 也一定也为单位下三角矩阵,则我们构造出了一组 A A A 的分解方案。

再证明分解方案的唯一性,分以下两种情况讨论:

  • A A A 可逆,

    • 假设分解不唯一,即 A = L 1 U 1 = L 2 U 2 A = L_1U_1 = L_2U_2 A=L1U1=L2U2,则 L 1 , U 1 , L 2 , U 2 L_1,U_1,L_2,U_2 L1,U1,L2,U2 均可逆,则有 U 1 U 2 − 1 = L 1 − 1 L 2 U_1U_2^{-1} = L_1^{-1}L_2 U1U21=L11L2,由于 U 1 U 2 − 1 U_1U_2^{-1} U1U21 是上三角矩阵, L 1 − 1 L 2 L_1^{-1}L_2 L11L2 是单位下三角矩阵,有 U 1 U 2 − 1 = L 1 − 1 L 2 = E U_1U_2^{-1} = L_1^{-1}L_2 = E U1U21=L11L2=E,所以 L 1 = L 2 , U 1 = U 2 L_1 = L_2, U_1 = U_2 L1=L2,U1=U2,与假设矛盾。
  • A A A 不可逆,

    • 由分块矩阵相关理论,可知: A = ( A n − 1 α β T a n , n ) = ( L n − 1 0 l T 1 ) ( U n − 1 u 0 T 0 ) A = \left( \begin{matrix} A_{n - 1}& \alpha\\ \beta^T& a_{n,n} \\ \end{matrix} \right) = \left( \begin{matrix} L_{n - 1}& 0\\ l^T & 1\\ \end{matrix} \right) \left( \begin{matrix} U_{n - 1}& u \\ 0^T& 0\\ \end{matrix} \right) A=(An1βTαan,n)=(Ln1lT01)(Un10Tu0)

    • 因而可得到以下关系式: A n − 1 = L n − 1 U n − 1 α = L n − 1 u ⇔ u = L n − 1 − 1 α β T = l T U n − 1 ⇔ l T = β T U n − 1 − 1 \begin{aligned} A_{n - 1} &= L_{n - 1}U_{n - 1}\\ \alpha = L_{n - 1}u &\Leftrightarrow u = L_{n - 1}^{-1} \alpha\\ \beta^T = l^TU_{n - 1} &\Leftrightarrow l^T = \beta^TU_{n - 1}^{-1}\\ \end{aligned} An1α=Ln1uβT=lTUn1=Ln1Un1u=Ln11αlT=βTUn11

    • 由可逆情况的证明, L n − 1 , U n − 1 L_{n - 1},U_{n - 1} Ln1,Un1 是唯一的,故 u , l T u, l^T u,lT 也可唯一确定,该情况得证。

  • 下面给出 L , U L,U L,U 的计算方法,显然有 l i , j = u j , i = 0 ( j > i ) , l i , i = 1 l_{i,j} = u_{j,i} = 0(j > i), l_{i,i} = 1 li,j=uj,i=0(j>i),li,i=1

    • i ≤ j i \le j ij 时,由 a i , j = ∑ k = 1 i l i , k u k , j = ∑ k = 1 i − 1 l i , k u k , j + u i , j a_{i,j} = \sum \limits_{k = 1}^{i}l_{i,k}u_{k,j} = \sum\limits_{k = 1}^{i - 1}l_{i,k}u_{k,j}+u_{i,j} ai,j=k=1ili,kuk,j=k=1i1li,kuk,j+ui,j,得 u i , j = a i , j − ∑ k = 1 i − 1 l i , k u k , j u_{i,j} = a_{i,j} - \sum \limits_{k = 1}^{i - 1}l_{i,k}u_{k,j} ui,j=ai,jk=1i1li,kuk,j,特别地,当 i = 1 i = 1 i=1 时,有 u i , j = a i , j u_{i,j} = a_{i,j} ui,j=ai,j

    • i > j i > j i>j 时,由 a i , j = ∑ k = 1 j l i , k u k , j = ∑ k = 1 j − 1 l i , k u k , j + l i , j u j , j a_{i,j} = \sum\limits_{k = 1}^{j}l_{i,k}u_{k,j} = \sum \limits_{k = 1}^{j - 1}l_{i,k}u_{k,j} + l_{i,j}u_{j,j} ai,j=k=1jli,kuk,j=k=1j1li,kuk,j+li,juj,j,得 l i , j = a i , j − ∑ k = 1 j − 1 l i , k u k , j u j , j l_{i,j} = \frac{a_{i,j} - \sum \limits_{k = 1}^{j - 1}l_{i,k}u_{k,j}}{u_{j,j}} li,j=uj,jai,jk=1j1li,kuk,j,特别地,当 j = 1 j = 1 j=1 时,有 l i , j = a i , j u j , j l_{i,j} = \frac{a_{i,j}}{u_{j,j}} li,j=uj,jai,j

  • 套用上式递推计算即可,时间复杂度 O ( n 3 ) \mathcal O(n^3) O(n3)

inline void LU_Factorization(double (*a)[N], int n)
{
	double b[N][N]; //由于 L,U 均为三角矩阵,可将两者同时存于一个矩阵中
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			if (i <= j)
			{
				b[i][j] = a[i][j];
				for (int k = 1; k < i; ++k)
					b[i][j] -= b[i][k] * b[k][j];
			}
			else 
			{
				b[i][j] = a[i][j];
				for (int k = 1; k < j; ++k)
					b[i][j] -= b[i][k] * b[k][j];
				if (fabs(b[j][j]) <= eps)
				{
					puts("No Solution");
					return ;
				}
				b[i][j] /= b[j][j];
			}
}

你可能感兴趣的:(学习笔记,概率论,算法)