数论
- 原根
- 上下取整运算
- 向下取整数论分块
- 数论分块记忆化搜索的时间复杂度
- 最小立方倍数问题
- 积性函数
- 完全积性函数
- 莫比乌斯函数
- 乘法逆元
- 一些环上定长位移问题
- 倍数和约数加总及还原技巧
- 最多k个不同数字组成的n的倍数
- n的因子中最长反链
- 1到n取k个数的可能的和
- 离散对数
- 最小分母分子
- 公约数公倍数问题
- 取模运算的一些问题
- 类欧几里德算法
- 一些快速计算的技巧
- 参考资料
原根
若正整数a与m互质,由欧拉定理知道存在某个正整数d,满足ad=1(modm),比如令d为 欧拉函数φ(m)。在所有满足上述条件的d中最小的可能取值记作δm(a),由于δm(a)≤φ(m),若等号成立,则成a是模m的原根。
存在模m的原根,当且仅当m为下列形式:2,4,pa,2pa。这里p是奇素数,a为正整数。
要找模m最小的原根,我们知道对于任意数a,都有δm(a)|φ(m)。因此我们可以先找到φ(m)的所有素数因子p1,p2,…,pk。根据资料,模p的最小原根应该是m14级别的,因此可以直接暴力从小到大枚举所有可能的原根。当我们枚举到a时,如果a不是原根,那么至少存在一个数1≤i≤k,使得δm(a)|φ(m)pi。此时必定有aφ(m)pi=1。因此我们可以通过这个条件来辨别一个数是否是原根。
上下取整运算
上下整除互换
对于任意整数a和正整数b,下面命题均成立。
⌈ab⌉=⌊a+b−1b⌋⌊ab⌋=⌈a−b+1b⌉下整除结结合性
对于任意整数a和正整数b,c,下面等式成立:
⌊⌊ab⌋c⌋=⌊abc⌋证明:
记p1=⌊⌊ab⌋c⌋,那么由整除的定义我们可以得到
⌊ab⌋=p1c+r1其中r1<c。进一步利用整除的定义得到:
a=(p1c+r1)b+r2=p1cb+r1b+r2其中r2<b。对公式两边同时除去bc得到:
abc=p1+r1b+r2bc由于:
r1b+r2<r1b+b=b(r1+1)≤bc因此我们认为⌊r1b+r2bc⌋=0。对左右两端都向下取整得到:
⌊abc⌋=p1+⌊r1b+r2bc⌋=p1=⌊⌊ab⌋c⌋命题得证。
上整除结合性
对于任意整数a和正整数b,c,下面等式成立:
证明:
⌈⌈ab⌉c⌉=⌈abc⌉记p1=⌈⌈ab⌉c⌉,那么由整除的定义我们可以得到
⌈ab⌉=p1c−r1其中r1<c。进一步利用整除的定义得到:
a=(p1c−r1)b−r2=p1cb−r1b−r2其中r2<b。对公式两边同时除去bc得到:
abc=p1−r1b+r2bc由于:
r1b+r2<r1b+b=b(r1+1)≤bc因此我们认为⌈−r1b+r2bc⌉=0。对左右两端都向上取整得到:
⌈abc⌉=p1+⌈−r1b+r2bc⌉=p1=⌈⌈ab⌉c⌉命题得证。
上下整除混用
命题:对于任意非负整数a和正整数b,c,都有⌈⌊ab⌋c⌉,⌊⌈ab⌉c⌋∈{⌊abc⌋,⌈abc⌉}
证明:
考虑到⌊abc⌋≤⌈⌊ab⌋c⌉,⌊⌈ab⌉c⌋≤⌈abc⌉,而⌊abc⌋+1≥⌈abc⌉,因此命题可以直接得证。
题目1:你需要操作一个数x,初始为0,经过若干次操作后,你要让它变成n。你可以执行四类操作:
- 将x变成2x,花费A
- 将x变成3x,花费B
- 将x变成5x,花费C
- 将x变成x+1或x−1,花费D
问达成目标最小的花费是多少,这里0≤n≤1018,0≤A,B,C,D≤109
一个直接的想法是DP,但是无路可走,因为数值范围太大了。但是实际上这个问题的解法非常简单。
首先费用最多为60A+D,因此结果一定可以用长整型来表示。
我们逆向计算结果,即我们手头的数为n,希望将其变成0。假设我们下一次操作为除上d,但是x不能被d整除,那么我们可以通过增大或减少d,让其能被整除。可以证明,我们最多将其增大到⌈xd⌉d或减少到⌊xd⌋d,因为我们每多增大d,不如在除d后增加1的费用低。因此我们给出了一个暴力递归算法。
记f(m)表示从m降低到0的最小费用。那么我们这里证明f的可能不同参数的数目。可以发现其参数只可能为
⌊m2α3β5γ⌋,⌈m2α3β5γ⌉两种形式。可以发现α≤60,β≤40,γ≤12,因此总状态的上限为28800。这里假如我们用哈希表来做缓存,时间复杂度可以优化到O((log2n)3)。
一道题目。
向下取整数论分块
面对下面这样的函数:
m∑i=1f(⌊ni⌋)其中m≤n。
其中计算f(x)的时间复杂度为O(1),求问你可以多快获得结果。
很显然暴力计算的时间复杂度将是O(n)。虽然称不上优秀,但是至少简单。但是假如n很大,那么计算起来就是一件轻松的事情了。
for(int i = 1; i <= m; i++)
{
sum += f(n / i);
}
通过观察可以发现存在很多i<j但⌊ni⌋=⌊nj⌋的情况,比如当n=4,i=3,j=4。
我们称⌊ni⌋为i的下整值。记下整值为x的所有的i的集合{i1,i2,…,ik}为x的导出集合,记作E(x)。
并且很容易发现拥有相同下整值的数值往往是连续的。实际上,若i<j且⌊ni⌋=⌊nj⌋成立时,对于任意i≤x≤j,都有⌊ni⌋≤⌊nx⌋≤⌊nj⌋,即⌊ni⌋=⌊nx⌋=⌊nj⌋。
我们可以将拥有相同下整值的值合并在一起计算。对于任意i,记j=⌊n⌊ni⌋⌋,可以推出:⌊ni⌋≤nj以及nj+1<⌊ni⌋。由后者可知max(E(⌊ni⌋))≤j,而由前者可知⌊nj⌋≤⌊ni⌋≤⌊nj⌋,结合得到j=max(E(⌊ni⌋))。
{min我们要做的就是枚举可能的下整值,并合并计算。
int sum = 0;
for(int i = 1, r; i <= m; i = r + 1)
{
int x = n / i;
r = min(m, n / x);
sum += f(x) * (r - i + 1)
}
上面我们就优化了原先的代码,加入了合并计算的能力。
代码优化是优化了,但是我们现在的时间复杂度究竟是多少呢?很显然时间复杂度为O(B),其中B是总共的分块数目,同样也是可能的下整值数量。考虑到对于1,2,\ldots,\sqrt{n},它们的下整值落在\{\left\lfloor\frac{n}{1}\right\rfloor,\left\lfloor\frac{n}{2}\right\rfloor,\ldots,\left\lfloor\frac{n}{\sqrt{n}}\right\rfloor\},而对于任意,而对于任意i>\sqrt{n},其下整值一定落在\{1,2,\ldots,\sqrt{n}\}中。因此可能的下整值最多有中。因此可能的下整值最多有2\sqrt{n}种。借此我们得到了时间复杂度的上限。借此我们得到了时间复杂度的上限O(\sqrt{n})。
题目1:要求计算\sum_{i=1}^n f(\left\lfloor \frac{n+i}{2i} \right\rfloor)。其中n\leq 10^{12},且f(x)可以O(1)求解。
这个和通常的数论分块公式有略微不同。我们可以用下整除的结合性进行化简:
\begin{aligned} \left\lfloor \frac{n+i}{2i} \right\rfloor &= \left\lfloor\frac{\left\lfloor \frac{n+i}{i} \right\rfloor}{2}\right\rfloor\\ &=\left\lfloor\frac{\left\lfloor \frac{n}{i} \right\rfloor+1}{2}\right\rfloor \end{aligned}我们记g(x)=\left\lfloor\frac{x+1}{2}\right\rfloor,那么我们可以得到f(\left\lfloor \frac{n+i}{2i} \right\rfloor)=fg(\left\lfloor \frac{n}{i} \right\rfloor),其中fg也是O(1)计算的函数。现在的问题就是一个普通的数论分块问题了,时间复杂度为O(\sqrt{n})。
题目2:给定n个有效区间,第i个有效区间为[l_i,r_i],要求计算有多少可能的周期T,满足1\leq T\leq m,且对于任意数x=T,2T,3T,\ldots,如果x\leq m,则至少一个有效区间包含x。其中1\leq n\leq 100,1\leq m\leq 10^9
首先我们可以认为所有有效区间都不相邻或相交(否则我们可以合并这些区间)。之后我们考虑所有的非有效区间[L_i,R_i],可以发现这样的区间最多只有n+1个。由于不能有x落在这样的区间中,因此,我们需要保证\left\lfloor \frac{L_i-1}{T} \right\rfloor=\left\lfloor \frac{R_i}{T} \right\rfloor。
于是现在我们等价于要计算有多少T,满足上面列出的O(n)个约束条件。可以发现每个约束条件(数论分块技巧)最多只会给出O(\sqrt{m})个可能区间,因此我们只需要合并这些区间即可。简单的方式就是用优先队列完成,时间复杂度为O(n\sqrt{m}\log_2n)。如果预处理所有的左右边界,并用基数排序进行排序,可以将时间复杂度降低到O(n\sqrt{m})。
题目3:要求计算\sum_{i=1}^nf(\left\lceil\frac{n}{i}\right\rceil),其中f(x)可以O(1)计算。且1\leq n\leq 10^{12}。
可以做如下转换:
\begin{aligned} \sum_{i=1}^nf(\left\lceil\frac{n}{i}\right\rceil)&=\sum_{i=1}^nf(\left\lfloor\frac{n+i-1}{i}\right\rfloor)\\ &=\sum_{i=1}^nf(1+\left\lfloor\frac{n-1}{i}\right\rfloor) \end{aligned}接下来就可以用快速数论变换了。时间复杂度为O(\sqrt{n})。
数论分块记忆化搜索的时间复杂度
考虑这样一道题目,我们定义函数f(x)=\sum_{i=2}^x f(\lfloor x/i \rfloor),特别的我们记f(1)=0。之后要求我们计算f(N)。其中1\leq N\leq 10^9。
根据数论分块的知识,我们知道总共只有O(\sqrt{N})个状态需要计算和保存,但是中间的计算量是多大的。一个粗略的估计是O(N),因为每个状态的计算量最多为O(\sqrt{N}),总共有O(\sqrt{N})个状态。
但是如果仔细观察可以发现时间复杂度会更加优秀。记L=\lfloor\sqrt{N}\rfloor,我们可以发现总共涉及的状态可以分为两类:
- 小于等于L的:1,2,\ldots,L
- 大于等于L的:\lfloor N/1 \rfloor,\lfloor N/2 \rfloor,\ldots,\lfloor N/L \rfloor
前者每个状态的计算时间复杂度的上界为O(\sqrt{L}),总的计算时间复杂度为O(L\sqrt{L})=O(N^{3/4})。
后者总的计算时间复杂度的总和可以用积分拟合:
\begin{aligned} \int_1^L \sqrt{N/x} \mathrm{d}x &= [2\sqrt{Nx}]_1^L\\ &=O(N^{3/4}) \end{aligned}因此总的计算时间复杂度为O(N^{3/4})。
提供一些题目
最小立方倍数问题
给定n=10000个数a_1,a_2,\ldots, a_n,每个数的范围是[1,10^{12}],要求为每个数a_i计算其最小的立方倍数(即a_i|y^3)。
这里有一个O(10^{6}+n\cdot10^4)的解法。
对于任意数x\in [1,10^{12}],要计算其最小立方倍数。记录Norm(x)表示x的没有立方因子的最大因子,记录Pair(x)表示Norm(x)的最小立方倍数。我们可以先找到所有x的素数因子 p\in [1,10^{12/3}]。之后利用这些因子可以得到Norm(x)。问题在于如何计算Pair(x)呢。
考虑数Norm(x)除去所有小于10^{12/3}的素因子后,最后剩下的数s,数s可能有三种情况:
- s是一个素数或1
- s是两个不同的素数的乘积
- s是某个素数的平方
对于第一和第二种情况,我们可以直接得出Pair(s)=s^2,而对于第三种情况,有Pair(s)=\sqrt{s}。
这样假如我们预先处理出所有不超过10^{12/2}的素数,并计算出他们的平方。那么我们可以在O(10^{12/3})时间复杂度内计算Pair(x)和Norm(x)。
我们记Cubic(x)表示x的最小立方倍数,那么可以得出:Cubic(x)=x\cdot Pair(x)。
这里的总的时间复杂度为O(10^{6}+n\cdot10^4)。
积性函数
对于定义于正整数集合上的函数f,如果对于任意两个互质的正整数a,b,有f(ab)=f(a)f(b),则称函数f为积性函数。
完全积性函数
对于定义于正整数集合上的函数f,如果对于任意两个正整数a,b,有f(ab)=f(a)f(b),则称函数f为完全积性函数,完全积性函数一定也是积性函数。
莫比乌斯函数
下面不讲原理,要看原理可以读《组合数学》这本书。
莫比乌斯函数定义为
\mu(x)=\left\{ \begin{array}{ll} 1 & ,x=1 \\ (-1)^k & ,x=p_1\ldots p_k,i\neq j \Rightarrow p_i\neq p_j \\ 0 & ,x=other \end{array} \right.容易发现莫比乌斯函数是积性函数。可以通过线性筛以线性时间复杂度预先算出。
下面说明莫比乌斯函数的主要用处:
[g=1]=\sum_{d|g}\mu(d)利用莫比乌斯函数还可以实现莫比乌斯反演,反演公式如下:
F\left(n\right)=\sum_{d|n}^{}{f\left(d\right)}\Leftrightarrow f\left(n\right)=\sum_{d|n}^{}{\mu\left(\frac{n}{d}\right)F\left(d\right)}\\ F\left(n\right)=\sum_{n|d}^{}{f\left(d\right)}\Leftrightarrow f\left(n\right)=\sum_{n|d}^{}{\mu\left(\frac{d}{n}\right)F\left(d\right)}莫比乌斯反演也可以用在集合上,公式如下:
F(S)=\sum_{T\subseteq S} f(T)\Leftrightarrow f(S)=\sum_{T\subseteq S} (-1)^{|S|-|T|} F(T)乘法逆元
x的乘法逆元是指这样的一个数y,满足x\cdot y=1。如果乘法满足结合律,那么x的乘法逆元是唯一的,假设z也是x的乘法逆元,那么y=y(xz)=(yx)z=z。
一般乘法逆元在素数的剩余类域中比较有用,因此下面仅讨论在模素数p意义下的乘法逆元。
如果仅想获得少数数的乘法逆元,可以利用费马小定理,由于x^{p-1}=1,因此乘法逆元为x^{p-2}。这里幂运算可以利用快速幂运算的技巧,因此时间复杂度为O(log_2p)。
如果不能保证p是素数,但是能保证x与p互质,那么可以利用扩展欧几里得算法,获得公式
ax+bp=1\rightarrow ax=1(\pmod p)而扩展欧几里得算法的时间复杂度为O(\log_2p)。
如果想要获得1,2,\ldots,n的乘法逆元,如果用上面的算法,需要O(n\log_2p)的时间复杂度。但是利用一些简单的性质,我们就可以得到乘法逆元的递推公式,将时间复杂度优化到O(n)。设k=\left\lfloor{p/x}\right\rfloor,记r=p(\pmod x),可以得出p=kx+r。
p=kx+r\\ \rightarrow kx+r=0(\pmod p)\\ \rightarrow r^{-1}x+k^{-1}=0(\pmod p)\\ \rightarrow r^{-1}+x^{-1}k^{-1}=0(\pmod p)\\ \rightarrow x^{-1}=-kr^{-1}(\pmod p)\\由于r<x,因此可以通过已知逆元r^{-1}推导出未知逆元x^{-1}。
一些环上定长位移问题
问题1:考虑有长度为m的环,环上的点记作0,1,2,\ldots, m - 1,你一开始站在位置0,你每次都可以向前移动a步,问你可以最终访问到哪些点
我们能访问的点为at\pmod m,其中a\geq 0,我们知道模运算实际上等同于减法运算,因此可以访问的点为at-km。考虑a和m的最大公约数g会发现g|at-km,由于扩展欧几里得原理告诉我们一定能到达g,因此可以推出所有g的倍数的点都可以被访问到。
问题2:考虑有长度为m的环,环上的点记作0,1,2,\ldots, m - 1,你一开始站在位置0,你每次都可以向前移动a_1,a_2,\ldots, a_n步,问你可以最终访问到哪些点
这个问题实际上就是上面问题的扩展,我们可以抵达的顶点是满足c_1a_1+c_2a_2+\ldots + c_na_n\pmod m=c_1a_1+c_2a_2+\ldots + c_na_n-km的顶点,记g=gcd(a_1,a_2,\ldots,a_n,m),显然我们只能访问g的倍数,并且扩欧保证了一定可以抵达g,因此可以访问所有g的倍数顶点。
问题3:一周有d天。有一副无向图G,其中包含n个顶点,记作1,2,\ldots,n,我们每一天都需要从所在的顶点移动到一个相邻顶点(由无向边连接)。我们从顶点1在一周的第1天出发,问回到顶点1可能在一周的哪些天。
首先我们只需要考虑顶点1所在的连通分量即可,之后都假设图G仅包含一个连通分量,且连通分量至少有两个顶点。首先我们可以在两个顶点之间不断来返,这样我们得到了一个回到原点的周期2,同时假如存在奇数长度的环,那么我们一定可以得到一个回到原点的奇数周期2k+1。之后利用问题2的结论可以得出,允许在顶点1度过的日子为gcd(2,2k+1,d)(如果没有奇数环则为gcd(2,d))的最大公约数。
问题4:一周有d天。有一副有向图G,其中包含n个顶点,记作1,2,\ldots,n,我们每一天都需要从所在的顶点移动到一个相邻顶点(由有向边连接)。我们从顶点1在一周的第1天出发,问回到顶点1可能在一周的哪些天。
这里我们只需要考虑包含1的强连通分量即可。下面我们假设图中仅包含一个强连通分量。很显然我们要做的就是求出所有从包含1的环的长度。如果图中的环的数目只有一个,这个方式是最佳的。否则可以建立nd个状态,状态(i,j)表示是否可能在一周的第j天抵达i,之后搜索即可。时间复杂度为O(nd)
问题5:一周有d天。有一副有向图G,其中包含n个顶点,记作1,2,\ldots,n,我们每一天都需要从所在的顶点移动到一个相邻顶点(由有向边连接)。我们从顶点1出发。之后回答Q个询问,询问的内容为如果我们如果出发日为s,是否有可能在第j天出现在顶点i
首先我们可以先解决问题4,这样我们可以得到我们可能出现在1的时间,由于这些时间中最小的数(除了0外)即为所有强连通分量的最小公约数g,之后我们简单记录一下每个顶点距离1的最短距离,记第i个顶点的距离为p_i。那么对于询问(s,i,j),我们只需要判断j-(s+p_i)\pmod d是否能整除g即可。
问题6:考虑有长度为m的环,环上的点记作0,1,2,\ldots, m - 1,你一开始站在位置0,你每次都可以向前或向后移动a_1,a_2,\ldots, a_n步,问你可以最终访问到哪些点
与问题2类似,只不过加入了向后移动。向后移动k步等价于向前移动m-k步,现在我们有2n个向前移动的方案。用问题2的方式解决即可。
倍数和约数加总及还原技巧
容斥原理统计倍数出现次数及还原
有一个数组a_1,a_2,\ldots, a_n。现在我们需要构建另外一个序列b_1,b_2,\ldots, b_n,其中b_i=\sum_{i|j}a_j。
这个问题很容易让人联想到快速沃什尔变换的或运算版本。那么这个问题是否能同样在O(n\log_2n)的时间复杂度内完成呢?答案是可以的,我们可以为每个下标暴力枚举其倍数并加和,总的时间复杂度为O(\frac{n}{1}+\frac{n}{2}+\ldots +\frac{n}{n})=O(n\ln n)。
现在考虑给定了b序列,我们是否能从b序列还原出a序列呢。事实上,也是可以的,注意到a_n=b_n,那么假设我们已经还原了a_{k+1},a_{k+2},\ldots, a_n,现在我们要还原a_k。容易发现b_k=\sum_{k|j}a_j\Rightarrow a_k=b_k-a_{2k}-a_{3k}-\ldots。暴力统计即可,时间复杂度也是O(n\ln n)。
容斥原理统计约数出现次数及还原
有一个数组a_1,a_2,\ldots, a_n。现在我们需要构建另外一个序列b_1,b_2,\ldots, b_n,其中b_i=\sum_{j|i}a_j。
从a到b非常容易,我们暴力枚举每个数x的倍数kx,之后将x的值贡献给kx即可。总的时间复杂度为O(n\ln n)。
那么从b到a呢,很显然a_1=b_1,假设我们已经知道a_1,a_2,\ldots, a_{k-1},要计算a_k,容易发现b_k=\sum_{j|k}a_j\Rightarrow a_k=b_k-\sum_{j|k\land j<k}a_j。从下到上枚举倍数贡献即可,时间复杂度也是O(n\ln n)。
统计带修改的倍数出现次数及还原
现在考虑有一个长度为n=10000的序列b_1,b_2,\ldots, b_n,其中b_i=\sum_{i|j}a_j。我们需要处理m=10000次请求,每次修改某个b_i,或者对于某个i,查询a_i。
这个有点像BIT哦。我们不能每次请求都利用容斥将b序列转换a序列,这样的时间复杂度为O(nm\log_2n),我们需要在O(nm)时间复杂度内完成。
一种简单的技术就是使用莫比乌斯反演来实时计算a_i。提前计算出莫比乌斯函数\mu,之后利用公式b_i=\sum_{i|j}a_j可以推出a_i=\sum_{i|j}\mu(\frac{j}{i})b_j。这样每次计算的时间复杂度为O(n)。
这边顺带一提,另外一个从a到b的问题:一个长度为n=100000的序列a_1,a_2,\ldots, a_n,其中b_i=\sum_{i|j}a_j。我们需要处理m=100000次请求,每次修改某个a_i,或者对于某个i,查询b_i。
实际上我们提前维护好每个数的约数信息,而一个数x的约数最多有2\sqrt{x},因此修改a_i的时候,只需要将所有i的约数j所代表的的b_j同步修改掉即可。这样每次修改的时间复杂度为O(\sqrt{n}),而每次查询的时间复杂度为O(1)。
统计带修改的约数出现次数及还原
现在考虑有一个长度为n=10000的序列b_1,b_2,\ldots, b_n,其中b_i=\sum_{j|i}a_j。我们需要处理m=10000次请求,每次修改某个b_i,或者对于某个i,查询a_i。
提前计算出莫比乌斯函数\mu,之后利用公式b_i=\sum_{j|i}a_j可以推出a_i=\sum_{j|i}\mu(\frac{i}{j})b_j。这样每次计算的时间复杂度为O(n)。
顺带一提,另外一个从a到b的问题:一个长度为n=100000的序列a_1,a_2,\ldots, a_n,其中b_i=\sum_{i|j}a_j。我们需要处理m=100000次请求,每次修改某个a_i,或者对于某个i,查询b_i。
这个问题需要我们使用分段处理的技术。对于一个数x<\sqrt{n},我们为其维护a_x信息。而对于数y\geq \sqrt{n},我们为其维护一个b_i'信息。当我们修改一个数x的时候,我们直接修改a_x,而当我们修改y的时候,由于其最多有\sqrt{n}个倍数,所以,保留枚举所有的倍数ky,并修改b_{ky}'即可。对于查询b_t,其值由两部分组成,一部分是所有小于\sqrt{n}的约数,一部分是大于等于\sqrt{n}的约数,前者我们暴力枚举a_1,a_2,\ldots,a_{\sqrt{n}}统计即可,后者已经记录在b'_t中了。因此不管是查询还是修改,时间复杂度都是O(\sqrt{n})。
最多k个不同数字组成的n的倍数
问题1:给定某个正整数n,找到某个n的倍数m,要求m\geq n且m的每个十进制位中只有最多k个不同的数字。其中n\leq 10^4
这个问题非常奇妙的一点,就是利用了{10\choose k}\leq {10\choose 5}=210。因此我们可以枚举所有0,1,\ldots, 9的大小为k的子集,并利用它构造一个n的倍数。
m是n的倍数,另外一个等价的含义就是m除上n的余数为0。
利用这一点,我们可以直接用最短路来实现。我们建立n个顶点,第i个顶点对应模上n的余数为i-1,之后将转移关系建立边,从起点跑最短路即可。
问题2:给定某个正整数n,找到某个n的倍数m,要求m\geq n且m的每个十进制位中只有最多4个不同的数字,且m的长度正好为L,其中n\leq 10^{12},L=100。
神奇的问题,可以去大佬Youtube上面的讲解。首先我们构造多个长度正好为L的仅有0和1组成的数字,并将其对n的余数保存到哈希表中。由生日悖论知道我们最多只需要O(\sqrt{n})个串就会发生一次碰撞。假设碰撞的两个值分别为a和b,我们可以证明a-b一定是n的倍数,且其中仅包含四个数字0,1,8,9。如果位数不够我们可以在后面补充上0即可。
n的因子中最长反链
考虑所有n的因子,我们在其上定义偏序关系,如果a|b,那么记作a\leq b。现在我们希望能得到n的因子中的最长反链。
我们可以利用匹配算法来求最大反链,时间复杂度为O(\sqrt{n}m),其中n是因子数,m是因子之间的偏序关系数量。但是如果n非常大的话,这个算法就不能用了。
有一个简单的结论,对于数x=p_1^{c_1}p_2^{c_2}\ldots p_k^{c_k},这里p_i是互不相同的素数,我们定义x的度数为deg(x)=c_1+c_2+\ldots+c_k。很显然度数相同的数之间不可能存在整除关系,因此度数为某个特定数的所有数都可以组成一条反链。而n的因子中最长反链可以由所有度数正好为\left\lfloor \frac{deg(n)}{2} \right\rfloor的因子组成。
我们可以利用分治+FFT算法高效算出反链的长度。
1到n取k个数的可能的和
在1,2,\ldots,n中取k个数,问有多少可能的和。事实上我们知道可能的和的下界为L=1+2+\ldots+k,而上界为R=(n-k+1)+\ldots+n。现在我们证明[L,R]之间的数都能取到。
实际上假设我们有k个指针,一开始分别指向1,2,\ldots,k。这时候和为L。接下来我们每次选择可以右移动的指向最靠右的指针,将其右移一步。很显然每一次移动k个指针指向的数的和都会增加1,而且这个流程直到所有指针分别指向(n-k+1),\ldots, n才会结束,即总和达到了R,因此我们就证明了L和R之间的每个数都可以取到。
离散对数
今天做了一道cf的题目,发现原来还有离散对数这么一个玩意,简单记录一下。
问题1:已知自然数a,b,p,其中a与p互质,求一个自然数x,使得a^x=b\pmod p。
由于a生成的循环群 \\{a^0,a^1,a^2,\ldots\\} 的大小一定不会超过p,因此我们仅需要考虑0\leq x\leq p-1。我们可以利用BSGS算法来计算。记k=\sqrt{p},我们提前计算a^0,a^1,\ldots,a^k,并将其存入哈希表中。由于假如x存在,那么记i=\left\lfloor \frac{x}{k}\right\rfloor,j=x \pmod k,满足x=ik+j。代入公式得到a^{ik+j}=b\Rightarrow a^{j}=ba^{-ik}。我们可以直接算出ba^{-ik}(由于a与k互质,可以利用扩欧得到a^{-1})并拿到哈希表中查找是否出现即可。
这样我们预处理的时间复杂度为O(\sqrt{p}),之后计算对数时暴力枚举的时间复杂度为O(\sqrt{p})。
问题2:已知自然数x,b,p,其中p是一个素数,求一个自然数a,满足a^x=b\pmod p。
由于素数一定有原根,我们可以找到它的原根g。由于原根的幂运算得到的集合大小为p-1,可以涵盖[1,p-1]之间的所有数。所以我们可以保证每个非0的数都可以表示为g的幂次。现在我们代入公式得到:
a^x=b \pmod p\\ \Rightarrow (g^{\log_gx})^a=g^{\log_gb}\pmod p\\ \Rightarrow g^{x\log_ga}=g^{\log_gb}\pmod p\\ \Rightarrow x\log_ga=\log_gb \pmod p-1\\ \Rightarrow x\log_ga+k(p-1)=\log_gb最后一项,我们可以用扩欧计算x与p-1的最大公约数t,如果t不能整除\log_gb,则无解,否则就可以利用扩欧直接得到\log_ga,之后用快速幂a=g^{\log_ga}。这里仅用到了离散对数算法和扩展欧几里得算法,时间复杂度为O(\sqrt{p}+\log_2p)。
问题3:已知自然数x,b,p,其中p是一个素数,求所有可能的自然数a,满足a^x=b\pmod p且a<p。
首先我们可以用问题2的方式得到一个解a_0,记g是p的一个原根。
由于g是原根,因此
a^x=b=a^x_0 \pmod p\\ \Rightarrow xlog_ga=x\log_g a_0 \pmod p-1\\ \Rightarrow xlog_ga=x\log_g a_0+k(p-1)\\ \Rightarrow log_ga=\log_g a_0 + \frac{k(p-1)}{x}很显然上面公式有解,前提条件是\frac{x}{gcd(p-1,x)}|k,因此我们可以记k=i\frac{x}{gcd(p-1,x)},那么有log_ga=\log_g a_0 + i\frac{(p-1)}{gcd(p-1,x)}。因此我们可以枚举i得到所有的解。
问题4:已知自然数a,b,p,求一个自然数x,使得a^x=b\pmod p
这个问题非常类似于问题1,但是区别在于a与p不一定互质。记d_1=gcd(a,p),那么公式变成:
\frac{a}{d_1}a^{x-1}=\frac{b}{d_1}\pmod \frac{p}{d_1}如果a和\frac{p}{d_1}依旧不互质,我们可以继续取最大公约数并化简公式,设最后得到的公式为:
\frac{a^k}{D}a^{x-k}=\frac{b}{D} \pmod \frac{p}{D}我们上面的公式只能计算\geq k的结果x,因此我们需要手动暴力枚举x=1,2,\ldots,k-1,试一试是不是有可行的。如果都不可行,那么如果b不能整除D,那么一定不会有\geq k的结果。否则我们将\frac{a^k}{D}取逆乘到右边去(此时\frac{a^k}{D}与\frac{p}{D}一定互质),问题就化成了问题1,求解即可。总的时间复杂度为O(\sqrt{p}+\log_2p)。
最小分母分子
问题1:给定两个分数\frac{b}{a}和\frac{d}{c},其中0\leq \frac{b}{a} \leq \frac{d}{c},求一个分数\frac{y}{x}满足\frac{b}{a}\leq \frac{y}{x}\leq\frac{d}{c},如果有多个,就求出其中分母最小的,如果有多个,就求出其中分子最小的。
我们先证明一个结论,设x'是所有满足条件的分数中的最小分母,而y'是所有满足条件的分数中的最小分子,那么\frac{b}{a}<\frac{y'}{x'}<\frac{d}{c}。设\frac{y_0}{x'}和\frac{y'}{x_0}是区间内的分数,那么有:
\frac{b}{a}\leq \frac{y'}{x_0} \leq \frac{y'}{x'} \leq \frac{y_0}{x'} \leq \frac{d}{c}因此结论得到证明。
现在记L = \left\lceil \frac{b}{a} \right\rceil,R = \left\lfloor \frac{d}{c} \right\rfloor。如果有L\leq R,那么我们可以取y=L,x=1就可以同时取到最小分母和最小分子了。
如果L>R,那么就意味着有L-1<\frac{b}{a}\leq \frac{d}{c} < L。我们可以从所有数中同时减去整数L-1,可以得到:
\frac{b-(L-1)a}{a}\leq \frac{y-(L-1)x}{x}\leq \frac{d-(L-1)c}{c}现在问题变成了处理解决\frac{b'}{a'}\leq \frac{y'}{x'} \leq \frac{d'}{c'},其中左右边界都是真分数。我们需要找到满足该不等式的最小分子y'和分母x',这时候一定有原问题的最小分子为y'+(L-1)x',而最小分母为x'。这是显然的,我们只是从原问题中减去了某个整数。
之后我们考虑将公式求反,我们通过求解\frac{c'}{d'}\leq \frac{x'}{y'} \leq \frac{a'}{b'}来找到最小分子和最小分母,由于最小分子和最小分母是同时取得的,因此很显然在这个不等式下求得的解中的最小分母就是原问题的最小分子,而最小分子就是原问题的最小分母。
这里有一个小坑,就是当d'=0的时候,我们不能颠倒左边的边界,但是此时问题变得非常简单,0\leq \frac{y'}{x'}\leq \frac{d'}{c'},我们可以得出y'=1,而x'=\left\lceil \frac{c'}{d'} \right\rceil。
这个算法与辗转相除法类似,每次递归的时候都会使得分母减少至少一半,因此总的时间复杂度为O(\log_2abcd)。随便写写就好了。
问题2:给定素数p,找到某个分数\frac{y}{x} = n \pmod p,其中0\leq y<x<p,如果有多个,则要求分母最小,如果还有多个,则要求分子最小。
由于分母一旦决定,分子也就被决定了,因此这里我们只需要保证分母最小即可。化简一下,可以得出
\frac{y}{x}=n\pmod p\\ \Rightarrow y=xn\pmod p\\ \Rightarrow y=xn-kp\\这里我们将约数条件代入,可以得出:
0\leq y=xn-kp < x < p\\ \Rightarrow \frac{p}{n}\leq \frac{x}{k} \leq \frac{p}{n-1}这里我们无需关心k=\left\lfloor \frac{xn}{p} \right\rfloor,因为上面的不等式已经包含了这个条件。现在我们只需要找到区间内有着最小的分子和分母的分数即可。这个实际上就是问题1了。
提供一道原题。
公约数公倍数问题
一些约定:
-
\phi(x)表示x的不同素因子数目。
-
\Phi(x)表示x的因子数目。
问题1:给定一组数a_1,\ldots, a_n,之后处理m个请求,第i个请求请求给定x_i,要求找出序列中与x_i互质的数的数目(即最大公约数为1)。满足1\leq a_i,n,m,x_i\leq M=300000
我们记f(x)=\sum_{i=1}^n[x\mid a_i],即序列中有多少数是x的倍数。
之后要统计序列中有多少数是与x是互质的,我们可以使用容斥定理,记录A_i表示能整除i的序列中所有数组成的集合,那么结果就是
|\bigcap_{d\mid x}\overline{A_d}|容斥的时间复杂度是O(2^{\phi(x)}),其中\phi(x)表示x的不同素因子数目。
还有一种方法就是借助莫比乌斯函数:
\sum_{i=1}^n[gcd(a_i,x)=1]\\ =\sum_{i=1}^n\sum_{d\mid gcd(a_i,x)}\mu(d)\\ =\sum_{d\mid x}\mu(d)\sum_{i=1}^n[d\mid a_i]\\ =\sum_{d\mid x}\mu(d)f(d)莫比乌斯函数的时间复杂度为O(\Phi(x)),其中\Phi(x)表示x的因子数目。
一般情况下容斥会快于莫比乌斯函数,因为不同素因子数目一般会非常小,比如对于300000以内的数,因子数最多可以达到180个,而不同素因子最多只有6个。而对于10^{18}以内的数,因子数最多可以达到103680,而不同素因子最多仅15个。
算法的总的时间复杂度为O(M\ln M+\sum_{i=1}^m2^{\phi(x_i)})。还是非常快的。
问题2:给定一组数a_1,\ldots, a_n,其中1\leq a_i,n\leq M=300000。要求找到最小的一个非空子集,使得子集中的的所有数的最大公约数为1。输出子集的大小。
这是CF的原题。
容易想到DP的方式,记录dp(i)表示最大公约数为i的因子的最小子集大小。注意到只有满足i\mid j,dp(j)才能对dp(i)产生贡献,所以我们需要遍历所有这样的j。现在只剩下一个问题了,怎么判断是否存在一个数a_k,使得gcd(a_k,j)\mid i。
我们可以发现,由于a_i\leq M=300000,而所有不超过M的正整数,最多有6个不同的素因子。因此我们可以提前计算出1到M中每个数的素因子,以及每个数的倍数在a序列中出现多少次。之后我们构造一个函数g,g(i)表示a序列中存在多少个数与i拥有大于1的公约数。函数g可以利用问题1的方式计算出来。
利用我们之前搞出来的g,就可以判断是否存在这样的a_k了。a_k存在,那么a序列中就必定存在至少一个数,与j/i互质,即g(j/i)<n。
总的时间复杂度为O(M\ln M+\sum_{i=1}^m2^{\phi(x_i)})。
问题3:给定一组数a_1,\ldots, a_n,要求找到两个数(两个数允许值相同,但是下标不能相同),要求这两个数互质。其中a_i,n\leq M=300000。
我们可以记录f(x)=\sum_{i=1}^n[x\mid a_i],即序列中有多少数是x的倍数,并动态维护它。
之后我们遍历a序列,假设遍历到a_i,我们可以利用容斥以及函数f快速判断已经扫描过的数有多少数与a_i互质,如果存在,我们可以用a_i与a_j(j<i)计算最大公约数,直到找到互质的数为之。否则我们就动态修改函数f。每次容斥花费的时间为2^{\phi(a_i)},而找互质的数的时间复杂度不会超过n\log_2M,每次动态修改f的时间复杂度为\Phi(a_i)。
总的时间复杂度为O(n\log_2n+M\ln M+\sum_{i=1}^n2^{\phi(a_i)})。
问题4:给定一组数a_1,\ldots, a_n,要求找到两个不同的元素(值允许相同,但是下标必须不同),使得它们的最大公约数最大。其中a_i,n\leq M=300000。
我们可以记录f(x)=\sum_{i=1}^n[x\mid a_i],即序列中有多少数是x的倍数,在计算这个的同时,我们为每个数维护一个大小为2的堆,将前两个贡献的数记录在堆中。
这样我们只需要从大到小遍历约数x,如果x的倍数出现的次数不少于2(f(x)\geq 2),那么我们就找到了所需的最大公约数,同时从堆中弹出两个顶部元素即可。
问题5:给定一组数a_1,\ldots, a_n,要求找到两个数(允许值相同,但是下标不允许相同),使得它们的最大公约数最小。其中a_i,n\leq M=300000。
问题2提出的DP实际就可以直接解决这个问题。我们只需要找到满足最小集合大小不超过2的所有可以构成的集合最小公约数中最小的那个即可,记这个数为g。
之后如何找到最大公约数为g的数呢,我们仅考虑a序列中能整除g的数,将它们都除去g后,我们要找的就是其中两个互质的数,这就是问题3。
问题6:给定一组数a_1,\ldots, a_n,要求找到两个不同的元素(值允许相同,但是下标必须不同),使得它们的最小公倍数最大。其中a_i,n\leq M=300000。
我们可以通过枚举最大公约数来找最大公倍数。
考虑最大公约数为g时,我们只需要考虑序列中那些能整除g的元素,我们将这些元素提取出来,并都除去g,现在我们要做的就是在这些被提取出来的元素中,找到乘积最大的互质的数即可。
通过问题3我们已经知道怎么找一对互质的数了,现在我们修改问题3提出的方法解决这个问题。
方法就是我们从大到小遍历元素,尝试对于每个元素,找到最大的与其互质的元素。具体做法,我们可以维护一个栈,每次扫描到新的元素a_i,我们就利用动态维护的倍数统计函数f和容斥检查一下栈中是否有元素与其互质,如果存在,就不断退栈,直到栈中不再存在与a_i互质的元素为止,同时我们计算a_i与栈中最大互质元素y的最大公倍数(二者的乘积再乘上g)。最后我们将a_i加入到栈中。当然在退栈的过程中,有一些没有找到对应的互质的数也可能会被弹出掉,但是它们实际上已经对最终结果没有影响了(与其互质的数必定小于a_i,且其本身又小于y,因此它的最大公倍数已经不会影响结果了)。
算法的总的时间复杂度为O(M\ln M + \sum_{i=1}^n 2^{\phi(a_i)}\Phi(a_i))
问题7:给定一组数a_1,\ldots, a_n,要求找到两个不同的元素(值允许相同,但是下标必须不同),使得它们的最小公倍数最小。其中a_i,n\leq M=300000。
类似于问题6,我们只需要修改算法,让算法从小到大遍历元素即可。
问题8:给出一组数a_1,\ldots,a_n,其中1\leq a_i\leq 10^{12},且n\leq 10^6。要求找到另外一个序列b_1,\ldots,b_n,满足1\leq b_i且gcd(b_1,\ldots,b_n)>1,称\sum_{i=1}^n\mid b_i-a_i\mid为序列b的费用,要求序列b的费用最小。如果有多个序列满足要求,任意输出一个序列即可。
问题要求我们找到一个素数,要求修正a_i为每个素数的倍数。可以很容易想到选择素数2,这时候得出序列b的序列一定不会超出n。
现在假设b'是费用最小的序列,现在我们发现至少有一半的数,对费用的贡献不超过1(否则费用就会超出n)。
于是我们可以选择找出任意一个对费用贡献不超过1的数,记作x,找到x-1、x、x+1的所有素因子,然后尝试找出最优解。
每一次我们都有1/2的概率得到最终结果。重复这个过程100次,结果错误的概率仅为2^{-100},完全可以忽略不计。
提供一道题目。
问题9:给定n个正整数a_1,\ldots,a_n,每个数的均不超过M=10^{18},要求求最大公约数。其中n\leq 10^8
需要注意到如果a,b彼此没有整除关系,那么一定有gcd(a,b)<a,b。因此我们维护前k-1个数的最大公约数g,现在要求前k个数的最大公约数,我们先校验一下a_n和g之间是否有整除关系,如果没有就跑一次欧几里得算法。可以知道欧几里得算法最大只会执行\log_2M次(但是这里会发现由于g是递减的,因此欧几里得算法总共占用的时间为O(\log_2M)),总的时间复杂度为O(n+\log_2M)
问题10:给定集合S=\{1,\ldots, n\},对于任意S的大小至少为2的子集C,其权值为W(C)=\max\{gcd(a,b)|a\neq b\land a,b\in C\}。现在给定K\geq 2,要求找到大小正好为K且权值最小的子集并输出。这里n\leq 10^6,2\leq K\leq n。
假设最终集合为T,且存在某个数b\in T,且b的某个约数a不在集合T中,那么我们可以用a替换T中的b。这个观察给了我们贪心算法正确性的保证。
可以发现对于最终集合T,如果x\in T是T中最大真因子最大的数,且记x的最大真因子为y。那么由于y已经存在于T中了,因此W(T)\geq gcd(x,y)=y。且由于没有任何数的最大因子超过y,因此就可以保证W(T)=y(两个不同数的最大公约数一定是某个数的真因子)。
上面的内容其实可以总结为任意一个集合中不同元素的最大公约数都不会超过集合中某个数的最大真因子。
现在我们知道了集合的权值最小,当且仅当集合中每个数的最大因子的最大值最小。这里我们可以设计一个漂亮的贪心过程:首先将所有数按照其最大真因子从小到大排序(这一步可以O(n\log_2n)完成)。之后我们取最大真因子最小的K个数组成集合。
这里还有一个疑问,我们组成的集合是否能保证一个数存在于集合中,其所有因子也都存在呢。这是一定的,因为一个数的最大真因子一定大于其任意因子,因此其因子一定早于这个数之前被加入到了集合中。
到此,算法结束。
提供一道题目。
取模运算的一些问题
设a为非负整数,b为正整数,那么一定有a=kb+c,其中0\leq c<b,k=\left\lfloor\frac{a}{b}\right\rfloor。我们记c=a\pmod b。
取模运算有一些比较有用的性质:
- 对于任意非负整数x,以及两个正整数0<a<b,一定有(x\pmod a)\pmod b=x\pmod a
- 对于任意正整数x,以及某个正整数a\leq x,一定有x\pmod a<\frac{x}{2}。
题目1:给定两个正整数a,b,统计在1,\ldots,n中有多少数x满足(x\pmod a)\pmod b=(x\pmod b)\pmod a
如果a=b,则每个数都可以。接下来我们始终假设a<b,根据取模运算的特性,可以得出(x\pmod a)\pmod b=x\pmod a。
我们将x表示为x=kb+r,则我们希望找到不同的(k,r)对,满足r=kb+r\pmod a。这时候显然有kb=0\pmod a,即lcm(a,b)| kb。我们枚举k即可。
时间复杂度为O(\log_2a+\log_2b)。
题目2:给定一组正整数a_1,\ldots,a_n,定义f(x,i)=x\pmod a_i+f(x\pmod a_i,i+1),且特殊的f(x,n+1)=0。要求对于所有非负整数x,找到f(x,1)的最大值。
记x_i=x_{i-1}\pmod a_i,特殊的x_0=x,记ans_i=x_i。我们可以定义g(i,j,k)表示选择第i个数x_i后,且x_i的取值范围为[0,j),则ans_1+\ldots+ans_i的值为i\cdot x_i+k。我们发现每个三元组(i,?,?)可以分解为两个(i+1,?,?),其中有一个三元组的第二维度的值为a_{i+1}-1。
我们用DP的方式来减少实际需要的三元组数量,只需要维护(i,j,k),其中满足随着j的增加k不断减少的三元组。同时可以发现(i,?,?)的三元组最多有i个。
到此我们给出了一个O(n^2\log_2n)时间复杂度的算法。(用平衡树维护三元组)
当然这还不够,我们发现如果(i,j,?)中j<a_{i+1},那么我们可以直接保留它到下一个状态,我们只分解那些满足j\geq a_{i+1}的三元组。
整个算法的过程中,只会插入n个三元组(i,a_i-1,?)。其余的三元组都是由它们分解来的,每一次分解,都会使某个三元组的第二个维度至少减少一半,因此总的时间复杂度为O(n\log_2n\log_2M),其中M=\max(a_1,\ldots,a_n)。
提供一道题目。
题目3:给定n个数a_1,\ldots,a_n,定义f(d)=\sum_{i=1}^n (\left\lceil\frac{a_i}{d}\right\rceil d-a_i),要求计算满足f(d)\leq k的最大的d。其中a_i\leq 10^9, 0\leq k\leq 10^{18},1\leq n\leq 100
我们先稍微转换一下公式:
\sum_{i=1}^n (\left\lceil\frac{a_i}{d}\right\rceil d-a_i) \leq k\\ \Rightarrow d\sum_{i=1}^n \left\lceil\frac{a_i}{d}\right\rceil \leq k+\sum_{i=1}^na_i注意到\left\lceil\frac{a_i}{d}\right\rceil的取值最多有2\sqrt{a_i}种可能性,因此\sum_{i=1}^n \left\lceil\frac{a_i}{d}\right\rceil最多有2n\sqrt{10^9}种可能性。我们可以用数论分块技术来确定每种可能性d的范围。于是最终的时间复杂度为O(2n\sqrt{10^9}\log_2n)。
类欧几里德算法
题目1:计算\sum_{i=0}^n\lfloor \frac{ai+b}{c}\rfloor,其中仅保证c>0,n\geq 0。
提供一道题目。
分几种情况讨论:
- 若a,b<0,则将式子转换成\sum_{i=1}^n-\lceil\frac{-a}{c}\rceil i-\lceil\frac{-b}{c}\rceil+\lfloor \frac{(a\%c)i+(b\%c)}{c}\rfloor
- 若a,b\geq c,则将式子转换成\sum_{i=1}^n\lfloor\frac{a}{c}\rfloor i+\lfloor\frac{b}{c}\rfloor+\lfloor \frac{(a\%c)i+(b\%c)}{c}\rfloor
- 否则0\leq a,b\lt c。记m=\lfloor \frac{ni+b}{c}\rfloor,根据下面公式进行转换:
可以发现每一次转换后,a,c会变成c\%a,a,这实际上类似于欧几里德算法,因此时间复杂度为O(\log_2a+\log_2c)。
一些快速计算的技巧
- 一个数能整除3当且仅当所有数的和能整除3。
- 记f(x)表示x的所有k进制位之和,可以发现f(x)=x\pmod{k-1},故f^{\infty}(x)=x\pmod{k-1},特殊的是如果k-1\mid x且x>0,则结果为k-1。
参考资料
- 原根wiki
- 二次剩余wiki
- 欧拉准则wiki
- 原根oi wiki
- Tonelli-Shanks算法
- 证明Tonelli-Shanks算法的简短博客
- (美)布鲁迪(Brualdi,R.A.)著.组合数学 第4版[M].机械工业出版社,2005:630.
- 欧拉函数 wiki
- 乘法逆元的计算
- BSGS