2024 暑假学习笔记

线性代数

向量#

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

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

比如说:

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

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

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

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

如图:

向量 ij 就是基向量。

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

  • a+b=(xa+xb,ya+yb)
  • ab=(xaxb,yayb)

如图:

a+b=c

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

向量的数乘:

  • pa=(pxa,pxb)

向量的点乘:

  • ab=|a||b|cos<a,b>

矩阵#

n×m 的矩阵。

n×n 的矩阵称之为方阵。

运算:

  • 矩阵的加法:按位相加即可。
  • 矩阵的数乘:按位相乘即可。
  • 矩阵的点乘:ci,j=ai,k×bk,j
  • 矩阵的转置:写为 ATAi,jT=Aj,i

基础矩阵:

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

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

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

行列式#

行列式仅针对方阵。

然后计算公式是:

det(A)=(1)σai

一些性质:

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

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

LGV 引理#

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

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

网格图是其中一种。

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

ans=|f(a1,b1)f(a1,b2)...f(a1,bn)f(a2,b1)f(a2,b2)...f(a2,bn)............f(an,b1)f(an,b2)...f(an,bn)|

即这个矩阵的行列式。

  • 证明:咕咕咕。

Matrix-Tree 定理#

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

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

  • 证明:咕咕咕。

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

  • 结论,外向:ans=det(A=DinL),内向:ans=det(A=DoutL)

  • 证明:咕咕咕。

BEST 定理#

最好的定理!!!

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

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

ans=Dxi=1n(outi1)

  • 证明:咕咕咕。

线性基#

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

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

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

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

拉格朗日插值#

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

f(x)=yiijxxjxixj

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

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

题目#

P2044 [NOI2012] 随机数生成器#

  • Description:给定 X0,m 和递推公式 Xn+1=(aXn+c)modm,求 Xn1n1018

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

P=[ac10]

然后初始矩阵为:

Q=[X0010]

然后答案就是 PnQ

P1962 斐波那契数列#

  • Description:求斐波那契数列第 n 项,1n109

直接上矩阵,不多说。

P2447 [SDOI2010] 外星千足虫#

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

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

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

P2886 [USACO07NOV] Cow Relays G#

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

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

P1397 [NOI2013] 矩阵游戏#

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

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

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

  • Description:让你求 i=0nCnkik+rmodp

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

P6573 [BalticOI 2017] Toll#

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

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

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

P3193 [HNOI2008] GT考试#

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

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

P7736 [NOI2021] 路径交点#

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

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

P3317 [SDOI2014] 重建#

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

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

eE(1pe)eEpe

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

然后共同除以 1pe,然后再乘上,变成:

(1pe)eEpe1pe

P4336 [SHOI2016] 黑暗前的幻想乡#

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

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

就是 n1 家公司边的生成树 - n2 + n3

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

组合计数

很简单的一些公式:

  • 递推公式:

(nm)=(n1m1)

  • 吸收恒等式:

(nm)=nm(n1m1)

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

i=mn(im)=(n+1m+1)

  • 平行恒等式:

i=0n(m+ii)=(m+n+1n)

  • 范德蒙德卷积:

i=0k(ni)(mki)=(n+mk)

  • 二项式定理:

(a+b)n=i=0n(ni)aibni

来看几个问题:

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

利用插板法,不难得到 (n1m1)

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

我们借 m 个元素过来,就显然是 (n+m1m1)

  • 问题 3:有不定方程 X1+X2+···+Xn=S,每个量都是非负整数,对于每个 Xi, 有限制 XiK ,计数这样的不定方程的解数。n,K105

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

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

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

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

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

(n,m)1=(n1,m1)1+(n1)×(n1,m)1

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

(n,m)2=(n1,m1)2+k×(n1,m)2

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

mn=i=0m(n,i)2mi

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

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

题目#

P1450 [HAOI2008] 硬币购物#

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

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

P1641 [SCOI2010] 生成字符串#

  • Description:n0m1,要求前 k1 的个数不能少于 n 的个数,问方案总数。

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

CF1278F Cards#

  • Description:有点复杂。

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

CF622F The Sum of the k-th Powers#

  • Description:求 i=1nik

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

[AGC013E] Placing Squares#

  • Description:有点复杂。

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

CF1895F Fancy Arrays#

  • Description:有点复杂。

考虑容斥,容斥下线后发现 xk+1 的贡献就是 (2k+1)n1,然后 x 的就弄个矩阵优化 DP 就好了。

[ABC281G] Farthest City#

  • Description:有点复杂。

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

数据结构

并查集#

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

种类并查集#

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

带权并查集#

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

笛卡尔树#

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

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

就是一个大根堆。

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

我们这么处理:

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(nlog2n) 做,应该不会卡。

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

左偏树#

先建一个堆。

定义 disti 表示 i 节点向右最多能走的节点个数。

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

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

可能更详细的

放个详细点的合并代码:

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 个历史版本更改而来要把 rtnow=rti

可持久化线段树#

一般也是更改节点可持久化,能用标记永久化就尽量用标记永久化,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(logn)
  • 更新:更新操作的时间复杂度为 O(logn)

应用场景:

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

平衡树#

一般都是写 FHQ Treap。

如果是 Splay 就是单旋双旋旋到顶,可以动态适配复杂度(如 O(logn) 的启发式合并),还有就是可以实现 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)

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

  • 辗转相除已经解出 bx1+(amodb)y1=gcd(a,b)
  • 我们要求 ax2+bx2=gcd(i,j) 的解。
  • 将上面化简得:bx1+(abab)x2
  • 化简:ax2+b(x2bab)
  • 然后继续递推就好了

Lucas 定理#

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

具体来说:

Cnm=Cn/pm/p×Cnmodpmmodp

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

CRT#

没用,不讲。

exCRT#

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

乘法逆元#

ax1(modp),则 ax 在模 p 意义下互为乘法逆元。

使用场景:

  • 若出现 (abmodp),不能等价于 amodpbmodp,此时可以用 a 乘以 b 的逆元 invba×invbmodp

求解逆元:

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

ax1(modp)ax+py=1

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

费马小定理:若 pP,且 gcd(a,p)=1,那么 ap11(modp)

  1. ap11(modp)ap2a1(modp),所以 ap2a 在模 p 意义下的逆元。
  • 1n 的所有整数的逆元(前提是存在):

ax1(modp),设 p=aq+r,其中 q=pa,r=pmoda,则 p=aq+r0(modp)arinvq(modp),inva=qinvpmoda,那么 inva=qainvpmoda

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

数论 2

基本数论#

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

欧拉函数:定义 φ(n)=i=1n[gcd(i,n)=1]

特殊点:

  • φ(1)=1

特殊性质:

  • 如果 pPφ(pn)=pn1(p1),证明:

p 不互质的 x 一共有 pn1 个(含有 p),所以 φ(p)=pnpn1,证毕。

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

分成 1x,2x3x... 若干个区间,我们设 1x 里有 p1,p2...ax 互质,那么 p1+x,p2+x... 仍然与 ax 互质,且只存在这些在 x2x 之间的数与 ax 互质,因为若有一个另外的数 p3,那么 p3x 理应与 ax 也互质,推翻假设,证毕。

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

欧拉函数是积性函数。

欧拉函数的求法:

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

φ(x)=xp11p1p21p2...

  1. 同线性筛求 1n 以内所有数值的欧拉函数,具体操作:
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 );
		}
	}
}

莫比乌斯反演#

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

就是 d|nμ(d)=[n=1]

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

然后一些典型推柿子:

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

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

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

题目#

整除分块#

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

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

P3327 [SDOI2015]约数个数和#

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

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

d 的性质:

d(ij)=x|iy|j[gcd(x,y)=1]

有了这个性质就秒秒钟切,先令 nm

i=1nj=1mx|iy|j[gcd(x,y)=1]

i=1nj=1m[x|i][y|j][gcd(x,y)=1]

根据直觉可得:

x=1ny=1mnxmy[gcd(x,y)=1]

x=1ny=1mnxmyp|gcd(x,y)μ(p)

x=1ny=1mnxmyp=1n[p|x][p|y]μ(p)

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

p=1nμ(p)i=1npj=1mpnpimpj

p=1nμ(p)i=1npnpij=1mpmpj

然后发现 npi=npi,此时你可以预处理出里面的数,外面和里面可以用整除分块搞。

P4213 【模板】杜教筛(Sum)#

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

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

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

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

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

h(n)=d|nf(d)g(nd)

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

h(n)=xy=nf(x)g(y)

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

考虑杜教筛的原理,先构造 h,g,使得 h=fg,则:

i=1nh(i)=i=1nd|nf(id)g(d)

i=1ng(d)i=1ndf(i)

i=1ng(d)S(nd)

然后:

S(n)=i=1nh(i)d=2ng(d)S(nd)g(1)

然后我们发现 S(nd) 是可以整除分块算的,我们只需快速算出 i=1nh(i) 即可,然后我们递归求解即可。

其复杂度为 O(n23)其实没啥用

P5221 Product#

感觉很难欸。

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

然后你发现 lcm 这东西,我熟啊,直接推柿子:

i=1nj=1nlcm(i,j)gcd(i,j)

i=1nj=1ni×jgcd(i,j)2

i=1nj=1ni×j(i=1nj=1ngcd(i,j))2

先看分子:

i=1nj=1ni×j

i=1n(in+n!)

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

(n!)n×i=1nin

(n!)2n

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

(i=1nj=1ngcd(i,j))2

d=1ni=1nj=1n[gcd(i,j)=d]gcd(i,j)

d=1ndi=1nj=1n[gcd(i,j)=d]

这里很有道理吧!

然后我们只看指数:

i=1nj=1n[gcd(i,j)=d]

i=1ndj=1nd[gcd(i,j)=1]

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

2×(i=1ndφ(i))1

那么我们便要求:

(n!)2n(d=1nd2×(i=1ndφ(i))1)2mod104857601

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

gcd(a,k)=1 时,有 ab=abmodφ(k),此时我们的模数时质数,所以给指数模一下 1048576011 就行了。

然后就是疯狂注意细节。

作者:alexande

出处:https://www.cnblogs.com/alexande/p/18294174

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Alexande  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示