Loading

2024 暑假学习笔记

线性代数

向量

我们定义向量是多维空间中一条带方向的线段,由于不太需要考虑其绝对位置关系,只考虑相对位置,一般都是平移到原点然后记录终点的坐标,记为 \(\vec x = (a_1, a_2, ..., a_n)\)

一般来说我们只探讨二维向量,因为是比较容易想的。

比如说:

我们可以称这个向量为 \(u\),也可以表示为 \(\vec B\),显然 \(\vec B = (2, 3)\)

同时在空间中也有一些特殊的向量,比如:

  • \(\vec 0\),就是原点,由原点指向原点,初始的向量。
  • \(\vec i\),在 \(x\) 轴方向前进一步的向量。
  • \(\vec j\),在 \(y\) 轴方向前进一步的向量。
  • \(\vec k, l, m ...\),在更高维度上前进一步的向量。

我们称这些向量为基向量。

如图:

向量 \(\vec i\)\(\vec j\) 就是基向量。

下面介绍一下向量的加减法则:

  • \(\vec a + \vec b = (x_a + x_b, y_a + y_b)\)
  • \(\vec a - \vec b = (x_a - x_b, y_a - y_b)\)

如图:

\(\vec a + \vec b = \vec c\)

如果是减法我们就加上其相反向量即可。

向量的数乘:

  • \(p \vec a = (px_a, px_b)\)

向量的点乘:

  • \(\vec a \cdot \vec b = |a| \cdot |b| \cdot \cos <a, b>\)

矩阵

\(n \times m\) 的矩阵。

\(n \times n\) 的矩阵称之为方阵。

运算:

  • 矩阵的加法:按位相加即可。
  • 矩阵的数乘:按位相乘即可。
  • 矩阵的点乘:\(c_{i, j} = \sum a_{i, k} \times b_{k, j}\)
  • 矩阵的转置:写为 \(A^T\)\(A^T_{i, j} = A_{j, i}\)

基础矩阵:

  • 单位矩阵:即主对角线上全为 \(1\),乘上任何矩阵都为它本身。
  • 零矩阵:全为 \(0\) 的矩阵,乘上任何矩阵都为零矩阵。

很多 DP 转移可以写成矩阵形式,由于矩阵乘法是有结合律的,所以可以快速幂做。

唯一有用的可能是高斯消元,那东西必须得看代码。

行列式

行列式仅针对方阵。

然后计算公式是:

\[\det(A) = \sum (-1)^{\sigma} \prod a_i \]

一些性质:

  • 交换两行,行列式值乘上 \(-1\)
  • 倍加,行列式值不变。
  • 和的拆分(一行或一列),直接拆分相加即可。
  • \(\det(AB) = \det(A) \det(B)\)

根据性质,可以用高斯消元把下面全部消成 \(0\),然后就只能取对角线上的值了。

LGV 引理

  • Pro:给定一个具有特殊性质的平面图 DAG,求从初始点集到结束点集的不相交路径方案数(两个点集点的个数相同)。

特殊性质:将起始点集以及结束点集像二部图一样分开排列,将线拉直不相交(点集内部不连边,有的话就去除,反正无论如何也不会走,因为不相交)。

网格图是其中一种。

  • 结论:定义 \(w(P)\) 为路径 \(P\) 上边的权的乘积(初始全为 \(1\)),\(f(s, t)\) 表示 \(s\)\(t\) 可能的路径之和,\(A\) 为初始点集,\(B\) 为结束点集,那么:

\[ans = \begin{vmatrix} f(a_1, b_1)& f(a_1, b_2)& ...& f(a_1, b_n) \\ f(a_2, b_1)& f(a_2, b_2)& ...& f(a_2, b_n)\\ ...& ...& ...& ...\\ f(a_n, b_1)& f(a_n, b_2)& ...& f(a_n, b_n) \end{vmatrix} \]

即这个矩阵的行列式。

  • 证明:咕咕咕。

Matrix-Tree 定理

  • Pro1:给你一张无向图,求其生成树个数。

  • 结论:令 \(D\) 为度数矩阵(主对角线为度数,带权),\(L\) 为邻接矩阵(可以有权),\(A = D - L\), 然后选择 \(A\) 中的一个格子,删掉它所在列和行,那么 \(ans = \det(A)\)

  • 证明:咕咕咕。

  • Pro2:给你一张有向图,求其外向/内向生成树个数。

  • 结论,外向:\(ans = \det(A = D_{in} - L)\),内向:\(ans = \det(A = D_{out} - L)\)

  • 证明:咕咕咕。

BEST 定理

最好的定理!!!

  • Pro:给你一张有向图,要你求从 \(x\) 出发的欧拉回路条数。

  • 结论:满足(\(D_x\) 为以 \(x\) 为根的内向生成树个数):

\[ans = D_x \prod_{i = 1}^n (out_i - 1) \]

  • 证明:咕咕咕。

线性基

基可以理解为基向量,线性基通常是关于异或的基。

然后就是做高斯消元,矩阵中剩下的数就是线性基所要的数了,然后表示一下即可。

然后我们可以用线性基求所有数的最大异或值和最小异或值,直接贪心即可。

如果要求第 \(k\) 小/大异或值,那么可以重新调整一下基然后再像 0/1 Trie 一样搞,不过通常只会考前两个。

拉格朗日插值

即给出 \(n\) 个点能确定一个 \(n - 1\) 次多项式,那么此时可以通过以下公式求这个多项式的点值:

\[f(x) = \sum y_i \prod_{i \ne j} \frac{x - x_j}{x_i - x_j} \]

其实通常我们取连续值然后用阶乘计算,那么计算单个就变成了 \(O(n)\) 的了。

一般如果有式子那么就猜多项式系数,直接开到最大的做就完事了。。。

题目

P2044 [NOI2012] 随机数生成器

  • Description:给定 \(X_0, m\) 和递推公式 \(X_{n + 1} = (aX_n + c) \mod m\),求 \(X_n\)\(1 \le n \le 10^18\)

首先这个题我们肯定要用矩阵快速幂,考虑构造矩阵:

\[P = \begin{bmatrix} a& c \\ 1& 0 \end{bmatrix} \]

然后初始矩阵为:

\[Q = \begin{bmatrix} X_0& 0 \\ 1& 0 \end{bmatrix} \]

然后答案就是 \(P^n \cdot Q\)

P1962 斐波那契数列

  • Description:求斐波那契数列第 \(n\) 项,\(1 \le n \le 10^9\)

直接上矩阵,不多说。

P2447 [SDOI2010] 外星千足虫

  • Description:给你若干个方程,形如一堆数的和 \(\bmod 2\) 的值,让你解方程,并推算出最早可以解出的时刻。

其实我们不难发现可以转化成异或方程组,然后我们可以高斯消元解出来,也可以用线性基求解。

但是题目太毒瘤了,我们需要用 bitset 优化一下常数。

P2886 [USACO07NOV] Cow Relays G

  • Description:给你一张有向图,让你求从 \(S\)\(T\) 恰好经过 \(K\) 条边的最短路。

我们定义广义矩阵乘法为 Floyd 的转移形式,然后我们把邻接矩阵对着这个东西求 \(K\) 次快速幂,最后 \((S, T)\) 的值就是答案了。

P1397 [NOI2013] 矩阵游戏

  • Description:有些复杂,可以自己看原题。

其实就是每一行矩阵快速幂,然后换行的时候乘上另外一个矩阵,对 \(m\) 行都做一遍这个事情,发现也可以矩阵快速幂,简单来说就是把式子完全展开然后构造两个矩阵硬算就好了。

P3746 [六省联考 2017] 组合数问题

  • Description:让你求 \(\sum_{i = 0}^{n} C_{nk}^{ik + r} \bmod p\)

其实我们组合意义一下,就是在 \(nk\) 个不同物品中选 \(t\) 个,且满足 \(t \bmod k = r\) 的方案数有多少,构造矩阵 DP 即可。

P6573 [BalticOI 2017] Toll

  • Description:有些复杂,可以自己看原题。

考虑到 \(k\) 最多为 \(5\),所以我们每 \(5\) 个构造一个与下 \(5\) 个的矩阵,然后求 \(s\)\(t\) 的最短路就是直接把两个所在矩阵中间的矩阵全部乘起来(广义矩阵乘法:Floyd 更新方式),然后看对应位置。

考虑到矩阵乘法是有结合律的,所以我们多次询问采用线段树进行解决。

P3193 [HNOI2008] GT考试

  • Description:有些复杂,可以自己看原题。

考虑设状态 \(f_{i, j}\) 为算到第 \(i\) 位,后 \(j\) 位与 \(t\) 匹配的方案数,这个时候我们需要求一个数组 \(g_{i, j}\),表示匹配了 \(i\) 位后再加一个字符能匹配 \(j\) 位的方案数,用 KMP 可以快速算出,然后对 DP 进行矩阵快速幂即可。

P7736 [NOI2021] 路径交点

  • Description:有些复杂,可以自己看原题。

我们发现本质就是 LGV 引理里的容斥,利用 LGV 引理做,完事。

P3317 [SDOI2014] 重建

  • Description:有些复杂,可以自己看原题。

我们发现 Matrix-Tree 定理本质上是要求生成树权乘积和的,那么我们考虑题目让我们求什么:

\[\prod_{e \notin E} (1 - p_e) \prod_{e \in E} p_e \]

就是还要保证生成树以外的边不被选中。

然后共同除以 \(1 - p_e\),然后再乘上,变成:

\[(1 - p_e) \cdot \prod_{e \in E} \frac{p_e}{1 - p_e} \]

P4336 [SHOI2016] 黑暗前的幻想乡

  • Description:有些复杂,可以自己看原题。

我们发现容斥 + Matrix-Tree 定理即可。

就是 \(n - 1\) 家公司边的生成树 - \(n - 2\) + \(n - 3\)

为什么要容斥呢?因为可能有多家公司用很多边,根据直觉,就应该这样容斥。

组合计数

很简单的一些公式:

  • 递推公式:

\[\binom{n}{m} = \binom{n - 1}{m - 1} \]

  • 吸收恒等式:

\[\binom{n}{m} = \frac{n}{m} \binom{n - 1}{m - 1} \]

  • 上指标求和(利用递推公式,加一项):

\[\sum_{i = m}^{n} \binom{i}{m} = \binom{n + 1}{m + 1} \]

  • 平行恒等式:

\[\sum_{i = 0}^n \binom{m + i}{i} = \binom{m + n + 1}{n} \]

  • 范德蒙德卷积:

\[\sum_{i = 0}^k \binom{n}{i} \binom{m}{k - i} = \binom{n + m}{k} \]

  • 二项式定理:

\[(a + b)^n = \sum_{i = 0}^n \binom{n}{i} a^i b^{n - i} \]

来看几个问题:

  • 问题 \(1\):给你 \(n\) 个相同的球和 \(m\) 个不同的盒子,问有多少种办法把这 \(n\) 个球放进盒子?盒子不可以空。

利用插板法,不难得到 \(\binom{n - 1}{m - 1}\)

  • 问题 \(2\):给你 \(n\) 个相同的球和 \(m\) 个不同的盒子,问有多少种办法把这 \(n\) 个球放进盒子?盒子可以空。

我们借 \(m\) 个元素过来,就显然是 \(\binom{n + m - 1}{m - 1}\)

  • 问题 \(3\):有不定方程 \(X_1+X_2+···+X_n = S\),每个量都是非负整数,对于每个 \(X_i\), 有限制 \(X_i \le K\) ,计数这样的不定方程的解数。\(n, K \le 10^5\)

考虑到容斥,所有的减去一个大于 \(K\),加上两个大于 \(K\),问题就把 \(S\) 减去若干个 \(K\) 就好了,但是需要注意有顺序,所以要乘上 \(\binom{n}{i}\)

然后是格路计数,就仿照卡特兰数,画两条线,然后上面那根对折以下就好了。

然后需要注意的是如果是有上下界,直接容斥即可,不要犹豫。

然后是斯特林数(由于不会打所以直接括号表示):

  • 第一类斯特林数:\((n, m)_1\) 表示 \(n\) 个不同元素构成 \(m\) 个圆排列方案。递推:

\[(n, m)_1 = (n - 1,m - 1)_1 + (n - 1) \times (n - 1, m)_1 \]

  • 第二类斯特林数:\((n, m)_2\) 表示 \(n\) 个不同元素构成 \(m\) 个非空子集。递推:

\[(n, m)_2 = (n - 1,m - 1)_2 + k \times (n - 1,m)_2 \]

需要特别特别特别记住的公式

\[m^n = \sum_{i = 0}^m (n, i)_2 m^{\underline i} \]

利用这个公式我们可以推很多组合意义。

比如 Card 和自然数幂和(虽然不如直接拉插)。

题目

P1450 [HAOI2008] 硬币购物

  • Description:给你 \(4\) 种硬币个数及面值,问恰好买 \(s\) 元物体有多少种方式?

考虑容斥,限制放的硬币种类,最开始再预处理一下多重背包就好了。

P1641 [SCOI2010] 生成字符串

  • Description:\(n\)\(0\)\(m\)\(1\),要求前 \(k\)\(1\) 的个数不能少于 \(n\) 的个数,问方案总数。

考虑组合意义,就是网格图画线,然后翻折一下求方案个数,然后稍微容斥一下即可。

CF1278F Cards

  • Description:有点复杂。

概率的题就是选中乘上选中的概率乘上没选中的乘上没选中的概率,推出来一下,然后用斯特林数幂展开,然后用二项式定理就做完了。

CF622F The Sum of the k-th Powers

  • Description:求 \(\sum_{i = 1}^{n} i^k\)

这个东西有个很明显的结论,就是它是 \(k + 1\) 次多项式,然后我们用拉格朗日插值插连续的 \(k + 2\) 个点值,算阶乘预处理就完了。

[AGC013E] Placing Squares

  • Description:有点复杂。

考虑分两类点,一类点是标记点,一类是没有标记点,然后我们设置两个不同的矩阵转移 DP,由于一共只会有线性个标记点,所以我们只需要做 \(k - 1\) 次快速幂和 \(k\) 次乘法就完了。

CF1895F Fancy Arrays

  • Description:有点复杂。

考虑容斥,容斥下线后发现 \(\le x - k + 1\) 的贡献就是 \((2k + 1)^{n - 1}\),然后 \(\le x\) 的就弄个矩阵优化 DP 就好了。

[ABC281G] Farthest City

  • Description:有点复杂。

考虑我们将图分层做 DP,那么第 \(i\) 层的点只会连向第 \(i + 1\) 层或者第 \(i\) 层自己,记录一下层的数量的当前层的点数就好了,注意 \(n\) 点必须要在最后一层,所以要把剩余点数减 \(1\)

数据结构

并查集

其实并查集的本质就是一颗树,只不过看你是只维护连通性还是维护其他信息。

种类并查集

就按照种类分层,然后不同种类之间互相连边,通常能够描述不同种类之间的矛盾关系。

带权并查集

就是树上每条边都带权,然后由于有边权,所以大部分情况下不能路径压缩,只能按秩合并。

笛卡尔树

考虑这样一个对于区间 \([l, r]\) 建树过程:

  • 拎出最大值做根节点。
  • 最大值两边的区间继续处理。

就是一个大根堆。

但是我们实际操作时不这么操作,因为复杂度是 \(O(n^2)\) 的。

我们这么处理:

for ( int i = 1; i <= n; i ++ ) {
        int tmp = cnt;
        while ( tmp && a[id[tmp]] > a[i] ) {
            tmp --;
        }
        if ( tmp ) {
            tree[id[tmp]].r = i;
        }
        if ( tmp < cnt ) {
            tree[i].l = id[tmp + 1];
        }
        id[++ tmp] = i, cnt = tmp;
    }

就每次试着往右边加,如果不行往上跳,复杂度 \(O(n)\)

如果忘了也可以直接分治 + 线段树 \(O(n \log_2 n)\) 做,应该不会卡。

哦,还有就是笛卡尔树的启发式搜索复杂度是对的(每次都往小的区间遍历)。

左偏树

先建一个堆。

定义 \(dist_i\) 表示 \(i\) 节点向右最多能走的节点个数。

然后合并堆的时候不断往下合并,如果左儿子的 \(dist\) 小于了 右儿子,就交换。

为什么复杂度是对的,因为每一层都是满的(会交换),所以 \(dist\) 最多不超过 \(\log_2 n\)

可能更详细的

放个详细点的合并代码:

int merge(int x, int y){
	if(!x || !y){
		return x + y; 
	}
	if(a[y] < a[x]){
		swap(x, y);
	}
	rs[x] = merge(rs[x], y);
	if(dist[ls[x]] < dist[rs[x]]){
		swap(ls[x], rs[x]);
	}
	dist[x] = dist[rs[x]] + 1;
	return x;
}

可持久化

可持久化的本质就是维护多个历史版本,将上一个版本需修改的节点原封不动的复制下载,继续更改。

然后可持久化数据结构复杂度必须是非均摊的,否则可以通过单一元素不同版本多次查询卡你(如 Splay)。

然后复杂度正确必须保证新增节点个数正确与结构正确。

可持久化字典树

一般是 O/1 Trie。

就先可持久化以下。

然后我们会发现这个东西对于某些差分问题有可减性,即对应节点相减可以把重复部分消掉,然后我们可以进行最大/最小/第 \(k\) 大的异或值的求解。

放个插入代码,可能有点不一样:

void insert ( int &node, int k, int x ) { // k 表示位数
    tree[++ cnt] = tree[node];
    tree[cnt].siz ++;
    node = cnt;
    if ( k == -1 ) {
        return ;
    }
    insert ( tree[node].ch[( x >> k ) & 1], k - 1, x );
}

上述代码如果是第 \(i\) 个历史版本更改而来要把 \(rt_{now} = rt_i\)

可持久化线段树

一般也是更改节点可持久化,能用标记永久化就尽量用标记永久化,pushdown 在一些情况下复杂度是对的也可以做。

可持久化权值线段树也利用可减性做题。

这个没有固定代码,就是修改的时候新增结点就好了。

可持久化平衡树

当分裂与合并的时候,复制一份,然后维护历史版本。

可持久化一般是 FHQ,但是这个东西的复杂度似乎不太对,但是很难卡,但是我知道可持久化 FHQ 的区间复制复杂度肯定是错的。

详细讲区间复制:把历史版本与当前版本合并就行。

来点合并分裂:

void split(int node, int val, int &r1, int &r2){
  if(!node){
    r1 = 0;
    r2 = 0;
    return ;
  }
  if(tree[node].val <= val){
    r1 = newnode();
    tree[r1] = tree[node];
    split(tree[r1].rs, val, tree[r1].rs, r2);
    update(r1);
  }
  else{
  	r2 = newnode();
  	tree[r2] = tree[node];
    split(tree[r2].ls, val, r1, tree[r2].ls);
    update(r2);
  }
}

int merge(int x, int y){
  if(!x || !y){
    return x + y;
  }
  if(tree[x].key > tree[y].key){
  	int node = newnode();
  	tree[node] = tree[x];
    tree[node].rs = merge(tree[node].rs, y);
    update(node);
    return node;
  }
  else{
  	int node = newnode();
  	tree[node] = tree[y];
    tree[node].ls = merge(x, tree[node].ls);
    update(node);
    return node;
  }
}

线段树 & 平衡树

线段树

主要是讲一些基本模型

考虑到线段树的基本结构,我们需要开 \(4n\) 个结点。

还有就是势能线段树,反正分析势能乱搞就对了(比如区间取 \(\min / \max\)

然后以下是基本总结:

线段树(Segment Tree)是一种基于树结构的数据结构,主要用于处理区间查询和区间更新操作。其基本原理如下:

数据结构:

  • 线段树通常是一棵平衡二叉树。
  • 每个节点代表数组中一段连续的区间,叶子节点对应数组中的单个元素。

构建过程:

  • 线段树从根节点开始,递归地将数组划分为更小的子区间,直到每个叶子节点表示数组中的一个单独元素。
  • 每个非叶子节点存储其子节点的合并结果(如区间和、最大值等)。

功能:

  • 区间查询:线段树能快速求解任意区间的查询问题,如区间和、最小值、最大值等。
  • 区间更新:支持快速更新数组中的元素,并且能够保持树结构的正确性。

操作复杂度:

  • 构建:线段树的构建时间复杂度为 \(O(n)\)\(n\) 是数组长度。
  • 查询:查询操作的时间复杂度为 \(O(\log n)\)
  • 更新:更新操作的时间复杂度为 \(O(\log n)\)

应用场景:

  • 适用于需要频繁进行区间查询和更新的场合,如动态统计区间内的最值、求和等问题。
  • 在解决动态数组查询问题时,特别是针对静态区间的查询和更新操作时表现优异。
  • 总结来说,线段树通过将数组元素组织成树结构,利用树的特性实现高效的区间查询和更新,是解决区间统计问题的一种经典数据结构。

平衡树

一般都是写 FHQ Treap。

如果是 Splay 就是单旋双旋旋到顶,可以动态适配复杂度(如 \(O(\log n)\) 的启发式合并),还有就是可以实现 LCT。

如果是替罪羊树就是重构,这一类平衡树在乱搞方面有极大优势(如 KDT 重构),但是基本不太好写,复杂度也有问题,但是好想。

然后附上分裂合并代码:

void split ( int node, int &r1, int &r2, int k ) {
    if ( !node ) {
        r1 = r2 = 0;
        return ;
    }
    if ( tree[node].val <= k ) { // 如果已经小于,整个左子树也是满足的,分裂右子树
        r1 = node;
        split ( tree[node].rs, tree[node].rs, r2, k );
    }
    else { //不满足,右子树肯定满足 > k,合并到 r2,分裂左子树
        r2 = node;
        split ( tree[node].ls, r1, tree[node].ls, k );
    }
    pushup ( node );
}

int merge ( int x, int y ) { // 需保证 x 子树值比 y 子数值小
    if ( !x || !y ) {
        return x + y;
    }
    if ( tree[x].key < tree[y].key ) { // 随机合并,期望 log
        tree[x].rs = merge ( tree[x].rs, y );
        pushup ( x );
        return x;
    }
    else {
        tree[y].ls = merge ( x, tree[y].ls );
        pushup ( y );
        return y;
    }
}

题目

P3261 [JLOI2015] 城池攻占

直接每个点维护左偏树,然后暴力往上合并即可,由于一个元素最多删除一次,所以复杂度有保证。

P1552 [APIO2012] 派遣

考虑到从下往上合并。

考虑一个人如果已经不能被选了,那么越往上走就越不能选,所以一个元素只会被丢一次,复杂度是对的。

P4755 Beautiful Pair

考虑笛卡尔树启发式合并,小的区间暴力,大的区间直接加入树状数组查询即可,注意要好好分析启发式合并的复杂度,否则会挂。

P3899 [湖南集训] 更为厉害

典题。

直接线段树合并统计贡献即可(发现深度是一段区间)。

P4592 [TJOI2018] 异或

对链和子树分别做可持久化 0/1 Trie 上的二分,然后就没了。

P3586 [POI2015] LOG

直接上平衡树统计一下贡献,判断一下贪心选择能否成功。

P4514 上帝造题的七分钟

考虑到空间限制不允许我们使用二维线段树,所以我们可以二维树状数组维护差分数组的四个项,然后二位前缀和一下。

数论

exgcd

通常来说,我们用辗转相除法求 \(\gcd (a,b)\)

根据裴属定理,\(ax + by = c\) 有整数解当且仅当 \(\gcd ( a, b ) | c\),所以问题就被简化成了 \(ax + by = \gcd (i, j)\)

然后我们考虑在辗转相除的过程中求,具体来说:

  • 辗转相除已经解出 \(bx_1 + (a \bmod b)y_1 = \gcd ( a, b )\)
  • 我们要求 \(ax_2 + b_x2 = \gcd ( i, j )\) 的解。
  • 将上面化简得:\(bx_1 + (a - b\lfloor \frac{a}{b} \rfloor)x_2\)
  • 化简:\(ax_2 + b(x_2 - b \lfloor \frac{a}{b} \rfloor)\)
  • 然后继续递推就好了

Lucas 定理

就是拆成 \(p\) 进制下每一位的组合数相乘。

具体来说:

\[C_{n}^m = C_{n / p}^{m / p} \times C_{n \bmod p}^{m \bmod p} \]

通常运用在模数较小,然后求逆元会挂得情况下(如果你看不懂,你得逆元得重新学)。

CRT

没用,不讲。

exCRT

就我们设两个东西公共解为 \(a\),列出二元一次方程,联立,然后不断重复解方程的过程就好了。

乘法逆元

\(ax \equiv 1 ( \mod p)\),则 \(a\)\(x\) 在模 \(p\) 意义下互为乘法逆元。

使用场景:

  • 若出现 \((\frac{a}{b} \bmod p)\),不能等价于 \(\frac{a \bmod p}{b \bmod p}\),此时可以用 \(a\) 乘以 \(b\) 的逆元 \(inv_b\)\(a \times inv_b \bmod p\)

求解逆元:

  • 求单个整数的逆元:
    • 扩展欧几里得算法:

\(ax \equiv 1 ( \mod p) \Rightarrow ax + py = 1\)

  1. 由此可知,乘法逆元不一定存在,因为方程并不一定有解。
  2. 乘法逆元若存在,那么有无数个,但在模 \(p\) 意义下只有一个(最小解)。
    • 有限情况下使用费马小定理:

费马小定理:若 \(p \in \mathbb{P}\),且 \(\gcd (a, p) = 1\),那么 \(a^{p - 1} \equiv 1 ( \mod p )\)

  1. \(a^{p - 1} \equiv 1 ( \mod p ) \Rightarrow a^{p - 2} \cdot a \equiv 1 ( \mod p )\),所以 \(a^{p - 2}\)\(a\) 在模 \(p\) 意义下的逆元。
  • \(1 \sim n\) 的所有整数的逆元(前提是存在):

\(ax \equiv 1 ( \mod p )\),设 \(p = a \cdot q + r\),其中 \(q = \lfloor \frac{p}{a} \rfloor, r = p \bmod a\),则 \(p = a \cdot q + r \equiv 0 ( \mod p)\)\(a \equiv -r \cdot inv_q ( \mod p ), inv_a = -q \cdot inv_{p \bmod a}\),那么 \(inv_a = \frac{-q}{a} \cdot inv_{p \bmod a}\)

  1. 注意前提要存在。
  • 线性求逆元:
  1. 先预处理阶乘,然后处理出 \(n!\) 的逆元,每次都乘上 \(i\) 就可以线性求解 \(1 \sim n\) 的阶乘的逆元了,这个用的一般比较多,但是大部分时候用 \(\log\) 就够了。

数论 \(2\)

基本数论

数论函数:定义域为正整数的函数。

欧拉函数:定义 \(\varphi(n) = \sum\limits_{i = 1}^n [\gcd(i, n) = 1]\)

特殊点:

  • \(\varphi(1) = 1\)

特殊性质:

  • 如果 \(p \in \mathbb{P}\)\(\varphi(p^n) = p^{n - 1}(p - 1)\),证明:

\(p\) 不互质的 \(x\) 一共有 \(p^{n - 1}\) 个(含有 \(p\)),所以 \(\varphi (p) = p^n - p^{n - 1}\),证毕。

  • \(a | x\),则 \(\varphi(ax) = ax\),证明:

分成 \(1 \sim x, 2x \sim 3x ...\) 若干个区间,我们设 \(1 \sim x\) 里有 \(p_1, p_2 ...\)\(ax\) 互质,那么 \(p_1 + x, p_2 + x ...\) 仍然与 \(ax\) 互质,且只存在这些在 \(x \sim 2x\) 之间的数与 \(ax\) 互质,因为若有一个另外的数 \(p_3\),那么 \(p_3 - x\) 理应与 \(ax\) 也互质,推翻假设,证毕。

  • \(\gcd ( a, b ) = 1\)\(\varphi(ab) = \varphi (a) \varphi (b)\),证明:

欧拉函数是积性函数。

欧拉函数的求法:

  1. 用定义法求单个 \(x\) 的欧拉函数 \(\varphi( x )\),公式如下:

\(\varphi(x) = x \cdot \frac{p_1 - 1}{p_1} \cdot \frac{p_2 - 1}{p_2} \cdot ...\)

  1. 同线性筛求 \(1 \sim n\) 以内所有数值的欧拉函数,具体操作:
void init () {
	phi[1] = 1;
	for ( int i = 2; i <= 1000000; i ++ ) {
		if ( !vis[i] ) {
			prime[++ cnt] = i;
			phi[i] = i - 1;
		}
		for ( int j = 1; j <= cnt && i * prime[j] <= 1000000; j ++ ) {
			vis[i * prime[j]] = true;
			if ( !( i % prime[j] ) ) {
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			phi[i * prime[j]] = phi[i] * ( prime[j] - 1 );
		}
	}
}

莫比乌斯反演

就数论函数典型套路,然后讲莫比乌斯反演。

就是 \(\sum_{d | n }\mu(d) = [n = 1]\)

然后记住一些数论函数间的乘积关系,不过大部分没用。

然后一些典型推柿子:

  • 转换变量,枚举最大公约数。
  • \(d(ij) = \sum_{x | i} \sum_{y | j} [\gcd(x, y) = 1]\)
  • 然后进行区域划分,分块套分块。
  • 整理式子,改变顺序。
  • 充分理解。

没有什么别的,就是推式子。

然后放一些经典例题(其实杜教筛学没学都一样,反正不会考)。

题目

整除分块

记录一下:\(r = n / ( n / l ), l = r + 1\)

主要就是利用除法的连续性。

P3327 [SDOI2015]约数个数和

又是一道清新的莫比乌斯反演(真是令人作呕)。

Tm 的想了 \(5\) 分钟没有想到怎么推柿子结果是 \(d\) 的性质没了解到。这道题会 \(d\) 的性质就秒秒钟切。

\(d\) 的性质:

\[d(i \cdot j) = \sum_{x | i} \sum_{y | j} [\gcd(x, y) = 1] \]

有了这个性质就秒秒钟切,先令 \(n \le m\)

\[\sum_{i = 1}^{n}\sum_{j = 1}^{m}\sum_{x | i}\sum_{y | j} [\gcd(x, y) = 1] \]

\[\sum_{i = 1}^{n}\sum_{j = 1}^{m} [x | i] [ y | j] [\gcd(x, y) = 1] \]

根据直觉可得:

\[\sum_{x = 1}^{n}\sum_{y = 1}^{m}\left\lfloor \frac{n}{x} \right\rfloor \left\lfloor \frac{m}{y} \right\rfloor [\gcd (x, y) = 1] \]

\[\sum_{x = 1}^{n}\sum_{y = 1}^{m}\left\lfloor \frac{n}{x} \right\rfloor \left\lfloor \frac{m}{y} \right\rfloor \sum_{p | \gcd(x, y)} \mu(p) \]

\[\sum_{x = 1}^{n}\sum_{y = 1}^{m}\left\lfloor \frac{n}{x} \right\rfloor \left\lfloor \frac{m}{y} \right\rfloor \sum_{p = 1}^n [p | x] [p | y] \mu(p) \]

很显然 \([p|x][p|y] = 0\) 是无用的:

\[\sum_{p = 1}^{n} \mu(p) \sum_{i = 1}^{\lfloor \frac{n}{p}\rfloor}\sum_{j = 1}^{\lfloor \frac{m}{p}\rfloor} \left\lfloor \frac{n}{p i} \right\rfloor\left\lfloor \frac{m}{p j} \right\rfloor \]

\[\sum_{p = 1}^{n} \mu(p) \sum_{i = 1}^{\lfloor \frac{n}{p}\rfloor} \left\lfloor \frac{n}{p i} \right\rfloor\sum_{j = 1}^{\lfloor \frac{m}{p}\rfloor}\left\lfloor \frac{m}{p j} \right\rfloor \]

然后发现 \(\left\lfloor \frac{n}{p i} \right\rfloor = \left\lfloor \frac{\frac{n}{p}}{i} \right\rfloor\),此时你可以预处理出里面的数,外面和里面可以用整除分块搞。

P4213 【模板】杜教筛(Sum)

包含着很多悲伤的故事(指卡 long long)。

考虑对于任意积性函数 \(f(x)\) 来说,如果你要求 \(S(n) = \sum\limits_{i = 1}^n f(i)\),你该怎么办。

正常做法是 \(O(n)\),毒瘤们非常不爽,于是有了杜教筛。

首先我们需要知道狄利克雷卷积是什么:

我们定义 \(f, g\) 为两个数论函数,则 \(h = f * g\),有如下等式:

\[h(n) = \sum_{d | n}f(d) g(\frac{n}{d}) \]

其实这个东西可以化成卷积的形式:

\[h(n) = \sum_{xy = n} f(x)g(y) \]

只不过上面那种形式比较通用。

考虑杜教筛的原理,先构造 \(h, g\),使得 \(h = f * g\),则:

\[\sum_{i = 1}^n h(i) = \sum_{i = 1}^{n} \sum_{d | n}f (\frac{i}{d}) g(d) \]

\[\sum_{i = 1}^n g(d) \sum_{ i = 1}^{\lfloor\frac{n}{d}\rfloor} f(i) \]

\[\sum_{i = 1}^n g(d) S \left(\left\lfloor\frac{n}{d}\right\rfloor\right) \]

然后:

\[S(n) = \frac{\sum_{i = 1}^n h(i) - \sum_{d = 2}^n g(d)S\left(\left\lfloor\frac{n}{d}\right\rfloor\right)}{g(1)} \]

然后我们发现 \(S\left(\left\lfloor\frac{n}{d}\right\rfloor\right)\) 是可以整除分块算的,我们只需快速算出 \(\sum_{i = 1}^n h(i)\) 即可,然后我们递归求解即可。

其复杂度为 \(O(n^{\frac{2}{3}})\)其实没啥用

P5221 Product

感觉很难欸。

首先根据我们的瞪眼法不难发现 \(104857601\) 是一个质数,这在后面非常有用。

然后你发现 \(\text{lcm}\) 这东西,我熟啊,直接推柿子:

\[\prod_{i = 1}^n\prod_{j = 1}^n\frac{\text{lcm}(i, j)}{\gcd(i, j)} \]

\[\prod_{i = 1}^n\prod_{j = 1}^n\frac{i \times j}{\gcd(i, j) ^2} \]

\[\frac{\prod_{i = 1}^n\prod_{j = 1}^n{i \times j}}{\left(\prod_{i = 1}^n\prod_{j = 1}^n{\gcd(i, j)}\right)^2} \]

先看分子:

\[\prod_{i = 1}^n\prod_{j = 1}^n{i \times j} \]

\[\prod_{i = 1}^n \left(i^n + n!\right) \]

解释一下这里为什么,你考虑当 \(i\) 不变的情况下,$j = 1, 2, ..., n $ 的情况就 OK 了。

\[(n!)^n \times \prod_{i = 1}^{n} i^n \]

\[(n!)^{2n} \]

然后再看分母(公式警告):

\[\left(\prod_{i = 1}^n\prod_{j = 1}^n{\gcd(i, j)}\right)^2 \]

\[\prod_{d = 1}^n\prod_{i = 1}^n\prod_{j = 1}^n [\gcd (i, j) = d]\gcd(i, j) \]

\[\prod_{d = 1}^n d ^ {\sum\limits_{i = 1}^n\sum\limits_{j = 1}^n[\gcd (i, j) = d]} \]

这里很有道理吧!

然后我们只看指数:

\[\sum\limits_{i = 1}^n\sum\limits_{j = 1}^n[\gcd (i, j) = d] \]

\[\sum\limits_{i = 1}^{\lfloor \frac{n}{d} \rfloor}\sum\limits_{j = 1}^{\lfloor \frac{n}{d} \rfloor}[\gcd (i, j) = 1] \]

我们发现,对于每个 \(i\) 来说,有贡献的只有与他互质的数,这种计数方法在 GCD SUM 这道题里有所体现:

\[2 \times \left(\sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor} \varphi(i)\right) - 1 \]

那么我们便要求:

\[\frac{(n!)^{2n}}{\left(\prod_{d = 1}^n d ^ {2 \times \left(\sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor} \varphi(i)\right) - 1}\right) ^2} \bmod 104857601 \]

然后你提交一发,发现 long long 会炸!所以我们需要用到欧拉定理:

\(\gcd (a, k) = 1\) 时,有 \(a^b = a^{b \bmod \varphi(k)}\),此时我们的模数时质数,所以给指数模一下 \(104857601 - 1\) 就行了。

然后就是疯狂注意细节。

posted @ 2024-07-10 15:31  Alexande  阅读(54)  评论(0)    收藏  举报