数论选讲(更新中)

版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/86761985

数论选讲

文章目录

(初等数论基础概念就不普及了)

一些前置姿势:

  1. 素数分布:素数有无限个,用 π ( x ) \pi(x) 表示小于 x x 的素数个数,则随着 x x 的增长,有 π ( x ) = Θ ( x ln x ) \pi(x)=\Theta(\frac{x}{\ln x}) ,同时蕴含常数 1 1
    这个结论可以用于估计某些与枚举素数有关的算法的复杂度。

  2. 算术基本定理,又称唯一分解定理。
    对于任意正整数 n n ,唯一存在以下分解: n = i p i k i n=\prod_i p_i^{k_i}
    其中所有 p i p_i 均为质数,且从小到大排列。

  3. O ( log a ) O(\log a) g c d gcd 算法和 e x g c d exgcd 算法默认大家都会,并且知道exgcd怎么求模意义下本质不同解的个数以及输出所有解。

  4. 同余的一些术语大家假装都听得懂吧(不然没法讲了。。。)


一,素数判定与因数分解

1.素数判定

首先大家都会线性筛 O ( n ) O(n) 筛出一部分质数

显然我们有试除法可以在 O ( n ) O(\sqrt n) 时间内判断一个数是否是质数。

当然可以通过只枚举质数优化至 O ( n ln n ) O(\frac{\sqrt n}{\ln n})

但是有的时候试除法复杂度也不够优秀,所以就需要Miller-Rabin算法。

1.1 Miller-Rabin

首先Miller-Rabin算法是一个随机算法,同时是一个概率算法(换句话说,有可能出错)

不过当测试次数足够多的时候,出错概率相当小就是了(反正脸黑的就多测几次就行了)。

前置姿势:

  1. 费马小定理:对于 p P , p a p\in \mathbb P,p\nmid a a p 1 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ,不过反过来不一定了。。。
  2. 二次探测:对于素数 p p ,能够满足 x 2 1 ( m o d p ) x^2\equiv 1\pmod p 的同余类 x x 只有 x 1 ( m o d p ) x\equiv 1\pmod p x p 1 ( m o d p ) x\equiv p-1\pmod p
二次探测证明:

x 2 1 ( m o d p ) x^2\equiv 1\pmod p ,则 x 2 = 1 + p k x^2=1+pk ,有 ( x + 1 ) ( x 1 ) = p k (x+1)(x-1)=pk

必须有 p x + 1 p\mid x+1 p x 1 p\mid x-1 ,分别对应 x p 1 ( m o d p ) x\equiv p-1\pmod p x 1 ( m o d p ) x\equiv 1\pmod p

1.1.1 算法流程(判断数n是否为质数):
  1. 对于偶数,直接跳出循环,当然同时可以把几个小质数的倍数一起特判。
  2. 否则找出 s , t s,t ,使得 n 1 = 2 s t n-1=2^st ,其中 2 t 2\nmid t
  3. 随机选取质数 a a
  4. 验证是否有 a n 1 1 ( m o d n ) a^{n-1}\equiv 1\pmod n ,如果不是则 n n 为合数
  5. 否则计算出 a t a^t ,然后进行 s s % n \%n 意义下的二次探测。
  6. 多重复几次就能大概率判断 n n 是否是一个质数。

一般我选择的是重复3~5次。

2.因数分解

同样的我们可以用试除法,复杂度同样不够优秀。。。

下面介绍一种根本没人用的方法:Fermat整数分解法。

2.1 Fermat整数分解法

首先,这个方法复杂度玄学,但是好于试除法。

怎么理解玄学这个事情。。。目前没有任何关于这个算法复杂度上界,下界,期望,渐进紧确界的任何断言或推论。

2.1.1 考虑分解数n:
  1. 如果是质数或1直接返回。
  2. 否则我们先把所有的质因子 2 2 剔除,从 a = n + 1 a=\lfloor\sqrt n\rfloor+1 开始往上枚举,计算 c = a 2 n c=a^2-n
  3. 如果存在正整数 b b 使得 b 2 = c b^2=c ,则有 n = ( a + b ) ( a b ) n=(a+b)(a-b) ,递归分解即可。
  4. c + a > n \lfloor\sqrt c\rfloor+a > n 时返回,说明原本的 n n 肯定是一个质数。

然后来看Pollard-Rho算法

2.2 Pollard-Rho算法

同样的,这个也是一个随机算法,一部分效率是要看脸的。

考虑对于 a , n N , g c d ( a , n ) n \forall a,n\in \mathbb N_*,gcd(a,n)\mid n

而本身 g c d gcd 的复杂度只有 O ( log n ) O(\log n) 级别,这给了我们启示,如何构造出一个靠谱的数 a a ,使得他包含 n n 的因子,就成了非常重要的问题。

一下直接假设 a a n n 的因子且 a n a\leq \sqrt n ,因为不是的那部分显然不重要。

我们在这个寻找过程中加入随机因素。

构造函数 f ( x , n ) = ( x 2 + c ) % n f(x,n)=(x^2+c)\%n ,其中 c c 是在每次Pollard-Rho之前决定好的在 [ 1 , n ) [1,n) 中的正整数,保证在 x = 1 x=1 的时候不会陷入死循环中。

构造序列 { x } \{x\} ,其中 x 1 = r a n d ( ) , x i = f ( x i 1 , n ) x_1=rand(),x_i=f(x_{i-1},n) 。我们可以认为它是近似随机的。(据说某版本C++的rand()也是这样实现的,不过略有区别)。

显然这个序列的轨迹要么是一个环,要么就是一个 ρ \rho 形(根据抽屉原理),且环长在 O ( n ) O(\sqrt n) 的级别内,这个环暂且称为一环。

还有,澄清一点,很多博客声称这个 f f 函数是单射函数。。。但是显然不是,否则怎么来的 ρ \rho 形轨迹。至少显然 f ( x , n ) = f ( n x , n ) f(x,n)=f(n-x,n) f f 就已经不是单射函数了。

然后考虑序列 { y } \{y\} ,其中 y i = x i % a y_i=x_i\%a (注意这里 a a 仍然是一个未知数),同理这个轨迹依然要么是环形要么是 ρ \rho 形,根据生日悖论,其环长在 O ( a ) O ( n 4 ) O(\sqrt a)\leq O(\sqrt[4]n) 规模。

考虑一环上两个不同的位置 x i ̸ = x j x_i\not=x_j ,如果它们对应二环上同一个位置 y i = y j y_i=y_j ,则有 j i = O ( a ) , a x j x i j-i=O(\sqrt a),a\mid |x_j-x_i|

这又给我们带来了一些启发。

如果我们能够找到一个 y i = x i % a y_i=x_i\%a ,然后 O ( a ) O(\sqrt a) 枚举 x j x_j ,当发现 1 < g c d ( n , x i x j < n ) 1< gcd(n,|x_i-x_j| < n) 的时候,我们就找到了一个因数。

但是注意,目前 a a 仍然是一个未知数。

可以采用两种办法,Floyd判圈算法或者Brent‘s找环算法’。

由于Floyd判圈跑的贼慢,所以这里只介绍Brent’s。

2.2.1 算法流程(已经验证n不是质数或1):
  1. 首先令 x 1 = r a n d ( ) x_1=rand() ,随机选择一个参数 c c ,当前环长 k = 1 k=1
  2. 然后向后扩展长度为环长的 x x 序列,记录 q = x 1 x i q=\prod|x_1-x_i|
  3. 计算 t = g c d ( q , n ) t=gcd(q,n) ,如果 t = 1 t=1 ,令 x 1 = x k + 1 x_1=x_{k+1} k = k 2 k=k*2 ,回到 2 2 步。
  4. 否则,如果 t = n t=n ,回到 1 1
  5. 不然,我们就找到了一个 n n 的因数 t t ,递归分解 t t n / t n/t 即可。
2.2.2 复杂度分析:

考虑处理一个因子时候的复杂度,若环长为 k k ,则枚举次数近似于:
T ( n ) = O ( k ) + O ( k 2 ) + O ( k 2 2 ) + + O ( k 2 z ) T(n)=O(k)+O(\frac{k}2)+O(\frac{k}{2^2})+…+O(\frac{k}{2^z})

这个数列是收敛的,化求和为积分后我们得到 T ( n ) = O ( k ) = O ( n 4 ) T(n)=O(k)=O(\sqrt[4]n) ,蕴含常数为 2 2 。套上一个 g c d gcd 就是 O ( n 4 log n ) O(\sqrt [4]n\log n)

考虑计算所有因子的复杂度:
T ( n ) O ( n 4 log n ) + T ( n 2 ) + T ( n 4 ) + + T ( n 2 z ) = O ( n 4 log n ) T(n)\leq O(\sqrt[4] n\log n)+T(\frac{n}2)+T(\frac{n}4)+…+T(\frac{n}{2^z})=O(\sqrt[4]n\log n)

然而实际上由于初期小因子较多,分解速度快,这个复杂度只有非常非常不走运的时候才会被卡到。

2.3 Quadratic Sieve Algorithm 二次筛法

以Fermat分解和二次剩余为基础的筛法,等熟练掌握Fermat和二次剩余之后再来看吧,这里放一个whzzt的博客:https://blog.csdn.net/whzzt/article/details/81069289

而且这个比Pollard-Rho快(小声BB),准确来说,复杂度远远优于Pollard-Rho。

不过比Pollard-Rho难写就是了。


二,中国剩余定理及其扩展

凭借强大的中国剩余定理,我们能够对很多同余问题进行快速的分解与合并,使得我们只需要考虑模数为质数的若干次幂的情况。

1. 一元线性同余方程

形如 f ( x ) a ( m o d n ) f(x)\equiv a\pmod n 的方程成为一元线性同余方程,其中 f ( x ) f(x) 中只包含关于 x x 的线性变换。

相信大家都会用ex欧几里得来解,这里就不赘述了。

2. 一元线性同余方程组

由若干个一元线性同余方程共同构成的方程组就是一元线性同余方程组 (废话)

一般把一元线性同余方程组写成这样:
{ x a 1 ( m o d m 1 ) x a 2 ( m o d m 2 ) x a t ( m o d m t ) \left\{ \begin{aligned} &x\equiv a_1\pmod {m_1}\\ &x\equiv a_2\pmod{m_2}\\ &…… \\ &x\equiv a_t\pmod{m_t}\\ \end{aligned} \right.

当对于 1 i , j t . i ̸ = j \forall 1\leq i,j \leq t.i\not=j ,都有 g c d ( m i , m j ) = 1 gcd(m_i,m_j)=1 的时候,以上方程恒有解,我们可以直接采用中国剩余定理求解。

3. 中国剩余定理CRT

考虑如果我们能够得到一个数列 { e } \{e\} ,对于 1 i , j t i ̸ = j \forall 1\leq i,j\leq t,i\not=j ,都有

{ e i 1 ( m o d m i ) e i 0 ( m o d m j ) \left\{ \begin{aligned} e_i\equiv 1\pmod{m_i}\\ e_i\equiv 0\pmod{m_j} \end{aligned} \right.

那么我们就能够很简单的得到答案了,就是

x i = 1 t e i a i ( m o d i = 1 t m i ) x\equiv \sum_{i=1}^te_ia_i\pmod{\prod_{i=1}^tm_i}

这个正确性是十分显然的,那么我们现在要做的就是构造出一个合法的 { e } \{e\} 序列。

M = i = 1 t m i M=\prod_{i=1}^tm_i M i = M m i M_i=\frac{M}{m_i} M i 1 M i 1 ( m o d m i ) M_i^{-1}M_i\equiv 1\pmod {m_i}

则可以直接得到满足条件的 e i M i 1 M i ( m o d M ) e_i\equiv M_i^{-1}M_i\pmod M

按照上面写的算一算就行了。

4. 扩展中国剩余定理exCRT

1 i , j t , i ̸ = j \exist 1\leq i,j \leq t,i\not=j ,有 g c d ( m i , m j ) ̸ = 1 gcd(m_i,m_j)\not=1 的时候,上面的方法不再适用了,我们需要一种更加通用的方法来计算方程组的解。

现在,考虑合并两个方程。

x a 1 ( m o d m 1 ) x\equiv a_1\pmod {m_1}

x a 2 ( m o d m 2 ) x\equiv a_2\pmod {m_2}

现在显然可以设: x = a 1 + y 1 m 1 = a 2 + y 2 m 2 x=a_1+y_1m_1=a_2+y_2m_2

而新的方程显然就是 x a 1 + y 1 m 1 a 2 + y 2 m 2 ( m o d l c m ( m 1 , m 2 ) ) x\equiv a_1+y_1m_1\equiv a_2+y_2m_2\pmod{lcm(m_1,m_2)}

显然现在需要解决的方程就是这个:

a 1 a 2 = y 2 m 2 y 1 m 1 a_1-a_2=y_2m_2-y_1m_1

相信大家都会直接用扩展欧几里得求解,随便解出一个合法的 y 1 , y 2 y_1,y_2 代回去就能得到一个新的方程,当上述方程无解时,方程组无解。

两两合并得到的最后一个方程就是答案。


三,原根,n次剩余与离散对数

1.原根

原根:对于正整数 n n ,它的原根是一个满足如下条件的正整数 a a g c d ( a , n ) = 1 gcd(a,n)=1 ,且 a , a 2 , a 3 . . . a ϕ ( n ) a,a^2,a^3...a^{\phi(n)} % n \%n 意义下两两不同余。

关于原根的几个结论(以下的 p p 都是指奇质数)

  1. 具有原根的数字只有以下几种形式: 2 , 4 , 2 p n , p n 2,4,2p^n,p^n ,且原根一定存在。
  2. n n 的最小原根大小是 O ( n 4 ) O(\sqrt[4]n)
  3. n n 有原根,则原根个数为 ϕ ( ϕ ( n ) ) \phi(\phi(n))
  4. g g p p 的原根,则 g g g + p g+p p 2 p^2 的原根。
  5. 对于 n N \forall n\in N_* ,若 g g p n p^n 的原根,则 g g g + p n g+p^n 中的奇数是 2 p n 2p^n 的原根。

证明:因为懒得写,咕了。

1.1 找原根

一般来说题目要求的数的原根都不会太大,所以暴力枚举 [ 2 , n 4 ] [2,\lfloor\sqrt[4]n\rfloor] 就行了。

问题在于怎么check,首先我们有欧拉定理可知: g c d ( a , n ) = 1 a ϕ ( n ) 1 ( m o d n ) gcd(a,n)=1\Rightarrow a^{\phi(n)}\equiv 1\pmod n

所以循环节长度 s s 必然有 s ϕ ( n ) s\mid \phi(n)

所以我们求出 ϕ ( n ) \phi(n) 的唯一分解,枚举所有的 p ϕ ( n ) p\mid \phi(n) ,然后判断 a ϕ ( n ) p ̸ 1 ( m o d n ) a^{\frac{\phi(n)}{p}}\not\equiv 1\pmod n ,所有 p p 上式都成立则找到一个原根 a a

1.2 指标

如果 n n 有原根 g g ,且

g r a ( m o d n ) g^r\equiv a\pmod n

成立,则称 r r 是以 g g 为底的 a a n n 的一个指标。

记作 r = i n d g a r=ind_ga

求指标可以用BSGS。

指标有以下性质:

  1. a b ( m o d p ) i n d g a i n d g b ( m o d ϕ ( n ) ) a\equiv b\pmod p\Leftrightarrow ind_ga\equiv ind_gb\pmod {\phi(n)}
  2. i n d g ( a b ) i n d g ( a ) + i n d g ( b ) ( m o d ϕ ( n ) ) ind_g(ab)\equiv ind_g(a)+ind_g(b)\pmod{\phi(n)}
  3. i n d g ( a k ) k i n d g ( a ) ( m o d ϕ ( n ) ) ind_g(a^k)\equiv k\cdot ind_g(a)\pmod{\phi(n)}

2.离散对数

离散对数问题:求解形如

A x B ( m o d C ) A^x\equiv B\pmod {C}

的同余方程。

我们先考虑 g c d ( A , C ) = 1 gcd(A,C)=1 的情况,显然此时只有当 g c d ( B , C ) = 1 gcd(B,C)=1 的时候有解。

2.1 BSGS

2.1.1 前置知识:指数模的周期性

A x A^x C C 取模的结果随着 x x 的变化具有周期性,最长周期为 C 1 C-1

证明:由抽屉原理易证。

所以我们只需要得到 x [ 0 , C 1 ] x\in [0,C-1] 的答案就行了。

那么现在怎么解决这个东西。。。(注意解出来不是一个同余等价类啊,就是说 C R T CRT 在这里毫无用武之地了)

朴素的枚举显然是 O ( C ) O(C) ,考虑优化。

这时候就要用到一种思想Baby-Step,Giant-Step,意味小步大步,简称为BSGS。民间称呼数不胜数:拔山盖世,北上广深,百度搜索谷歌搜索

我们将这 C C 个数分为 n n 组,每组 m = C n m=\lceil\frac{C}n\rceil 个数。对每一组进行询问,在这组 m m 个数内是否有答案。

这时候答案可以看成是: A i m y B ( m o d C ) A^{im-y}\equiv B\pmod C

由于 g c d ( A , C ) = 1 gcd(A,C)=1 ,所以 A A % C \%C 意义下是存在逆元的,这时候就可以将上面这个方程等价转化为 A i m A y B ( m o d C ) A^{im}\equiv A^{y}B\pmod C

我们将 y = 1 , 2 , . . . m y=1,2,...m 的所有 A y B A^yB 存到一个哈希表内,然后 O ( n ) O(n) 枚举 i i 计算 A i m A^{im} 的值,再到哈希表中查询就可以得到我们的答案了。

总的复杂度 O ( C n ) + O ( n ) O(\frac{C}n)+O(n) ,当 n = C n=\sqrt C 的时候取得最小值,此时复杂度为 O ( C ) O(\sqrt C)

2.2 exBSGS

现在尝试解决当 g c d ( A , C ) ̸ = 1 gcd(A,C)\not=1 时候的离散对数问题。

在已经有了解决 g c d ( A , C ) = 1 gcd(A,C)=1 的问题的方案的时候,我们考虑将这个问题转化成 g c d ( A , C ) = 1 gcd(A,C)=1 的情况。

现在将原方程 A x B ( m o d C ) A^x\equiv B\pmod C 进行一点转化:
A x = B + C y A^x=B+Cy

d 1 = g c d ( A , C ) , C 1 = C d 1 , B 1 = B d 1 d_1=gcd(A,C),C_1=\frac{C}{d_1},B_1=\frac{B}{d_1} ,得到等价方程:

A d 1 A x 1 = B 1 + y C 1 \frac{A}{d_1}A^{x-1}=B_1+yC_1

d 2 = g c d ( A , C 1 ) , C 2 = C 1 d 2 , B 2 = B 1 d 2 d_2=gcd(A,C_1),C_2=\frac{C_1}{d_2},B_2=\frac{B_1}{d_2} ,得到等价方程:

A 2 d 1 d 2 A x 2 = B 2 + y C 2 \frac{A^2}{d_1d_2}A^{x-2}=B_2+yC_2

一直处理到 n n ,直到 d n + 1 = 1 d_{n+1}=1
则我们得到等价方程:

A n i = 1 n d i A x n = B n + y C n \frac{A^n}{\prod_{i=1}^nd_i}A^{x-n}=B_n+yC_n

其中 B n = B i = 1 n d i , C n = C i = 1 n d i B_n=\frac{B}{\prod_{i=1}^nd_i},C_n=\frac{C}{\prod_{i=1}^nd_i}

如果 B n B_n 不是整数肯定就没有解了。

D = A n i = 1 n d i D=\frac{A^n}{\prod_{i=1}^nd_i} ,显然 g c d ( D , C n ) = 1 , g c d ( A , C n ) = 1 gcd(D,C_n)=1,gcd(A,C_n)=1 ,即 D , A D,A % C n \%C_n 意义下存在逆元。

得到等价方程: D A x n B n ( m o d C n ) D\cdot A^{x-n}\equiv B_n\pmod{C_n} ,即

A x n B n D 1 ( m o d C n ) A^{x-n}\equiv B_n\cdot D^{-1}\pmod {C_n}

现在已经转化成了 g c d ( A , C n ) = 1 gcd(A,C_n)=1 的形式了,直接上普通的BSGS就行了。

注意我们还需要先做一次 O ( log C ) O(\log C) 的枚举(显然 n n 不会超过 O ( log C ) O(\log C) ),因为最后的答案可能没有 n n 大,就是说可能 g c d gcd 的影响还没有完全消除,就已经得到 B B 了。

3.n次剩余

n n 次剩余问题:求解形如

x n a ( m o d m ) x^n\equiv a\pmod m

的同余方程。

利用CRT我们可以把问题分解,设 m m 的唯一分解式为 m = i = 1 t p i k i m=\prod_{i=1}^tp_i^{k_i}

{ x n a ( m o d p 1 k 1 ) x n a ( m o d p 2 k 2 ) x n a ( m o d p t k t ) \left\{ \begin{aligned} &x^n\equiv a\pmod {p_1^{k_1}}\\ &x^n\equiv a\pmod {p_2^{k_2}}\\ &…… \\ &x^n\equiv a\pmod {p_t^{k_t}} \end{aligned} \right.

解完这 t t 个方程后用CRT合并回去就行了。

所以接下来只讨论求解 x n a ( m o d p k ) x^n\equiv a\pmod {p^k} 这样的方程。

3.1 p为奇质数的情况

此时 p k p^k 的原根一定存在,设这个原根为 g g

首先我们令 g c d ( a , p k ) = 1 gcd(a,p^k)=1 ,不然我们就看一看 a a 当中有几个 p p ,两边同时除去,如果总数不是 n n 的倍数,那么原方程无解,否则我们总是可以通过在最后的 x x 中乘上若干个 p p 来得到我们要的解。

首先我们用BSGS解一下这个方程:

g t a ( m o d p k ) g^t\equiv a\pmod {p^k}

解出来 t t 之后,我们设 x g z ( m o d p k ) x\equiv g^z\pmod{p^k} ,则我们得到这个方程:

g z n g t ( m o d p k ) g^{zn}\equiv g^{t} \pmod {p^k}

所以有 z n t ( m o d ϕ ( p k ) ) zn\equiv t\pmod{\phi(p^k)}

扩欧解一解就行了。

注意,扩欧没有解肯定就只能咕咕咕了。

3.2 p=2的情况

有一个很显然的结论: x n a ( m o d 2 k ) x^n\equiv a\pmod {2^k} 的不充分前提是 x n a ( m o d 2 k 1 ) x^n\equiv a \pmod{2^{k-1}}

而如果 x n a ( m o d 2 k 1 ) x^n\equiv a\pmod{2^{k-1}} ,则要么 x n a ( m o d 2 k ) x^n\equiv a\pmod{2^k} 要么 ( x + 2 k 1 ) n a ( m o d 2 k ) (x+2^{k-1})^n\equiv a\pmod{2^{k}}

解的总个数不会很多(实测证明这个剪枝跑得飞快),暴力 d f s dfs 即可。

4.二次剩余

二次剩余问题:求解形如

x 2 a ( m o d m ) x^2\equiv a\pmod m

的同余方程。

首先,还是先分解成 x 2 a ( m o d p k ) x^2\equiv a\pmod {p^k} 来处理。

当然可以按照 n n 次剩余的套路来解。

不过我们现在有优秀的 O ( log p k ) O(\log p^k) 的解法。

4.1 p为奇质数且k=1的情况

现在的方程就是这样的: x 2 a ( m o d p ) x^2\equiv a\pmod p

4.1.1 解的存在性

首先考虑一个问题,无解。

为什么会无解?显然 x 2 ( p x ) 2 ( m o d p ) x^2\equiv (p-x)^2\pmod p 根据抽屉原理,这样下来总有一些抽屉是空的,这就是无解的情况。

x 2 a ( m o d p ) x^2\equiv a\pmod p 有解,则称 a a % p \%p 意义下的二次剩余类。
否则称 a a % p \%p 意义下的二次非剩余类。

我们定义勒让德符号( L e g e n d r e   s y m b o l Legendre\text{ }symbol ):
( a p ) = { 1 a % p 1 a % p 0 a 0 ( m o d p ) \Big(\frac{a}{p}\Big)=\left\{ \begin{aligned} 1 &&&a是\%p下的二次剩余 \\ -1 &&&a是\%p下的二次非剩余 \\ 0 &&& a\equiv0\pmod p \end{aligned} \right.

我们有欧拉准则: ( a p ) a p 1 2 ( m o d p ) (\frac{a}p)\equiv a^{\frac{p-1}2}\pmod p

证明: 首先 p a p\mid a 的时候欧拉准则显然成立。

否则由费马小定理我们有 a p 1 1 ( m o d p ) a^{p-1}\equiv 1\pmod p

所以必然有 a p 1 2 ± 1 ( m o d p ) a^{\frac{p-1}{2}}\equiv \pm1\pmod p

先证明 ( a p ) = 1 (\frac{a}{p})=1 的情况

必要性: a a % p \%p 意义下的二次剩余,即 x , x 2 a ( m o d p ) \exist x,x^2\equiv a\pmod p

那么我们有

a p 1 2 ( x 2 ) p 1 2 x p 1 1 ( m o d p ) a^{\frac{p-1}2}\equiv (x^2)^{\frac{p-1}2}\equiv x^{p-1}\equiv 1\pmod p

必要性证明完毕。

充分性: p p 有一个原根 g g ,那么必然有 g i a ( m o d p ) g^i\equiv a\pmod p

则:

g i ( p 1 ) 2 1 ( m o d   p ) g^{\frac{i(p-1)}2}\equiv 1(mod\text{ }p)

由于 g g 为原根,所以必然会有 ( p 1 ) i ( p 1 ) 2 (p-1)\mid\frac{i(p-1)}{2}

i i 是偶数。

必然存在解 x 0 g i 2 ( m o d p ) x_0\equiv g^{\frac{i}2}\pmod p

充分性证毕。

那么 ( a p ) 1 ( m o d p ) (\frac{a}p)\equiv-1\pmod p 的情况也就十分显然了。

首先由费马小定理 a p 1 2 ± 1 ( m o d p ) a^{\frac{p-1}{2}}\equiv \pm 1\pmod p

由于前面的欧拉准则在 ( a p ) = 1 (\frac{a}{p})=1 的必要性,二次非剩余的情况下 x 2 a p 1 1 ( m o d p ) x^2\equiv a^{p-1}\equiv -1\pmod p ,显然不可能,违反了费马小定理。

4.1.2 Cipolla算法

以下所有运算均在 % p \%p 意义下进行

b 2 a w ( m o d p ) b^2-a\equiv w\pmod p ,其中 w w % p \%p 意义下的二次非剩余

由于 w w % p \%p 意义下不存在平方根,类似于虚数设 i = w i=\sqrt w 。类似于复数重新定义 % p \%p 意义下一个数为 ( a , b ) (a,b) ,即 a + b w a+b\sqrt w

接下来定义一个代数系统 < G , + , × > <G,+,\times> 满足:

( a , b ) + ( c , d ) = ( a + c , b + d ) (a,b)+(c,d)=(a+c,b+d)

( a , b ) × ( c , d ) = ( a c + b d w , a d + b c ) (a,b)\times (c,d)=(a c+b d w,a d+b c)

显然 G G 是一个环,不知道什么是环的自行百度,百度百科

既然有结合律了。就可以快速幂。

那么有结论:上述方程的解为 x = ( b + i ) p + 1 2 x=(b+i)^{\frac{p+1}2}

证明如下:

x 2 = ( b + i ) p + 1 = ( b + i ) p ( b + i ) \begin{aligned} x^2 &=(b+i)^{p+1} \\ &=(b+i)^p(b+i) \end{aligned}

其中,由二项式定理

( b + i ) p = k = 0 p C p k b k i p k (b+i)^p=\sum_{k=0}^pC_p^kb^ki^{p-k}

显然当 k ̸ = 0  or  p k\not= 0\text{ or }p C p k 0 ( m o d p ) C_p^k\equiv 0\pmod p

所以有

( b + i ) p b p + i p b p 1 b + w p 1 2 i b i ( m o d p ) (b+i)^p\equiv b^p+i^p \equiv b^{p-1}b+w^{\frac{p-1}2}i\equiv b-i\pmod p

这个式子的推出同时用到了费马小定理和二次非剩余的特殊性质。

所以可以推出:

x 2 ( b i ) ( b + i ) b 2 w a ( m o d p ) x^2\equiv (b-i)(b+i)\equiv b^2-w\equiv a\pmod p

4.1.3 寻找二次非剩余

由于前文已经叙述了,由于有 t 2 ( p t ) 2 ( m o d p ) t^2\equiv (p-t)^2\pmod p ,所以二次剩余的数量不会超过 O ( p 2 ) O(\frac{p}2) ,我们随机出来一个数就有将近 1 / 2 1/2 的概率是二次非剩余,直接随机寻找即可,期望次数为 2 2

4.2 p为奇质数且k>1的情况

4.2.1 解的存在性

进行解的存在性判断稍微麻烦了一些

设: a = p c m a=p^cm p m p\nmid m

c k c\ge k ,直接返回0。

c < k c < k ,有解多了一个前提条件: c % 2 = 0 c\%2=0

必要性证明:

设:

x 0 2 a ( m o d p k ) x_0^2\equiv a\pmod{p^k}
x 0 = p t n , n % p ̸ = 0 x_0=p^tn,n\%p\not=0

所以

x 0 2 = p 2 t n 2 x_0^2=p^{2t}n^2

2 t < k 2t < k ,有如下推论:

( p 2 t s )   %   p k = p 2 t ( s   %   p k 2 t ) (p^{2t}s)\text{ }\%\text{ }p^k=p^{2t}(s\text{ }\%\text{ }p^{k-2t})

所以 p 2 t a p^{2t}|a ,同时我们也可以这样将原方程化为

x 0 2 a / p c ( m o d p k c ) x_0^2\equiv a/p^c\pmod {p^{k-c}}

当新方程有解时,原方程也有解,将上面欧拉定则里面推理用的 p 1 p-1 换成 ϕ ( p k c ) \phi(p^{k-c}) 就行了。

最后解为 x = x 0 × p c 2 x=x_0\times p^{\frac{c}2}

所以接下来只讨论 p a p\nmid a 的情况。

4.2.2 从Cipolla推进

现在求解方程

x 2 a ( m o d p k ) x^2\equiv a\pmod {p^k}

其中 p a p\nmid a

先解出

r 2 a ( m o d p ) r^2\equiv a\pmod p

那么有

( r 2 a ) = k p ( r 2 a ) k 0 ( m o d p k ) (r^2-a)=kp\Rightarrow (r^2-a)^k\equiv 0\pmod {p^k}

( r 2 a ) k t 2 u 2 a ( m o d p ) k (r^2-a)^k\equiv t^2-u^2a\pmod p^k

我们有

( r a ) k = t u a ( r + a ) k = t + u a (r-\sqrt a)^k=t-u\sqrt a \\ (r+\sqrt a)^k=t+u\sqrt a

这个运算仍然在扩域后进行。(可以证明上式显然是成立的,由二项式定理)

最终我们有

t 2 u 2 a ( m o d p k ) t^2\equiv u^2a\pmod {p^k}

解出来的方程就是

t 2 u 2 a ( m o d p k ) t^2u^{-2}\equiv a\pmod {p^k}

显然

g c d ( t , p ) = g c d ( u , p ) = 1 gcd(t,p)=gcd(u,p)=1

所以逆元用扩展欧几里得求一下就行了。

4.3 p=2的情况

4.3.1 解的存在性

处理幂的方法与上面这种情况差不多,我们效仿上面先化成

x 2 a ( m o d 2 k ) , 2 a x^2\equiv a \pmod{2^k},2\nmid a

那么现在呢,没有欧拉准则了啊。

从特殊情况谈起,先打一个表,把那些有解的 a a 找出来

k 有解的a
1 1
2 1
3 1
4 1,9
5 1,9,17,25
6 1,9,17,25,33,41,49,57

似乎当且仅当 a 1 ( m o d 8 ) a\equiv1\pmod{8} 的时候有解啊。。。

实际上,我们有如下的蕴含关系:

a 1 ( m o d 8 ) x , x 2 a ( m o d 2 k ) a\equiv1\pmod{8}\Leftrightarrow \exist x,x^2\equiv a\pmod{2^k}

必要性: 由于存在解 x 0 , x 0 2 a ( m o d 2 k ) x_0,x_0^2\equiv a\pmod{2^k}

由于 g c d ( a , 2 ) = 1 gcd(a,2)=1 ,所以 g c d ( x 0 , 2 ) = 1 gcd(x_0,2)=1 ,不妨设 x 0 = 2 t + 1 x_0=2t+1

所以

a x 0 2 ( 2 t + 1 ) 2 4 t ( t + 1 ) + 1 ( m o d 2 k ) a\equiv x_0^2\equiv (2t+1)^2\equiv 4t(t+1)+1\pmod{2^k}

显然 8 4 t ( t + 1 ) 8\mid 4t(t+1) ,所以 a 1 ( m o d 8 ) a\equiv 1\pmod8

充分性: 由下面叙述的求解方法易证。也就是说,在能够求出解的情况下,解总是存在 (废话)

4.3.2 找规律+递推

1. k 2 k\le2
特判。

2. k = 3 k=3
二次剩余方程 x 2 a ( m o d 2 3 ) x^2\equiv a\pmod {2^3} 有解,当且仅当 a 1 ( m o d 2 3 ) a\equiv 1\pmod{2^3} ,且本质不同的解有四个: ± 1 , ± 5 \pm1,\pm5

换句话说,我们可以将这个解记为 x = ± ( x 3 + t 3 × 2 2 ) , t 3 Z , x 3 = 1  or  5 x=\pm(x_3+t_3\times 2^2),t_3\in\mathbb Z,x_3=1\text{ or }5

3. k > 3 k > 3

假设我们已经知道方程 x 2 a ( m o d 2 q 1 ) x^2\equiv a\pmod{2^{q-1}}
的解,显然解可以表示成 x q = ± ( x q 1 + t q 1 × 2 q 2 ) , t q 1 Z x_q=\pm(x_{q-1}+t_{q-1}\times 2^{q-2}),t_{q-1}\in \mathbb Z

考虑如何推导出 x q x_q t q t_q

为了方便,后面记 a i = a % 2 i a_i=a\% 2^i

对于一个 x 2 a ( m o d 2 q 1 ) x^2\equiv a\pmod{2^{q-1}} x q 1 x_{q-1} 来说,在 % 2 q \%2^q 意义下,只可能有:

x q 1 2 a q 1 ( m o d 2 q ) x q 1 2 a q 1 + 2 q 1 ( m o d 2 q ) \begin{aligned} &x_{q-1}^2\equiv a_{q-1} &\pmod{2^q} \\ 或是&x_{q-1}^2\equiv a_{q-1}+2^{q-1}&\pmod{2^q} \end{aligned}

所以我们就要求出合适的 t q 1 t_{q-1} 的值,先代入方程 x 2 a ( m o d 2 q ) x^2\equiv a\pmod {2^q}

( x q 1 + 2 q 2 × t q 1 ) 2 a q ( m o d 2 q ) x q 1 2 + 2 q 1 t q 1 a q ( m o d 2 q ) t q 1 a q x q 1 2 2 q 1 ( m o d 2 ) \begin{aligned} (x_{q-1}+2^{q-2}\times t_{q-1})^2 & \equiv a_q &\pmod{2^q} \\ x_{q-1}^2+2^{q-1}t_{q-1}&\equiv a_q &\pmod{2^q} \\ t_{q-1}& \equiv \frac{a_q-x_{q-1}^2}{2^{q-1}} &\pmod{2} \end{aligned}

所以满足要求的 t q 1 = a q x q 1 2 2 q 1 + 2 × t q , t q Z t_{q-1}=\frac{a_q-x_{q-1}^2}{2^{q-1}}+2\times t_q,t_q\in \mathbb{Z}

回到方程 x 2 a ( m o d 2 q ) x^2\equiv a \pmod{2^q} 它的解就是

x = ± ( x q 1 + a q x q 1 2 2 + 2 k 1 × t k ) , t k Z x=\pm(x_{q-1}+\frac{a_q-x_{q-1}^2}{2}+2^{k-1}\times t_k),t_k\in\mathbb{Z}

q = 3 q=3 的情况开始一路递推即可。

P.S.:本来还想讲三次剩余娱乐一下,结果发现没有题。。。

其实三次剩余比二次剩余要简单,有兴趣的可以自行了解一下。


四,常见积性函数以及性质

几个定义:

  1. 数论函数:定义域为 N + \mathbb{N^+} ,陪域为复数域的函数。
  2. 积性函数:对于数论函数 f ( n ) f(n) ,如果对于 a , b , N + , g c d ( a , b ) = 1 \forall a,b,\in \mathbb{N^+},gcd(a,b)=1 ,都有 f ( a b ) = f ( a ) f ( b ) f(ab)=f(a)f(b) ,则 f ( n ) f(n) 是一个积性函数。
  3. 完全积性函数:对于数论函数 f ( n ) f(n) ,如果对于 a , b , N + \forall a,b,\in \mathbb{N^+} ,都有 f ( a b ) = f ( a ) f ( b ) f(ab)=f(a)f(b) ,则 f ( n ) f(n) 是一个完全积性函数。

证明一个函数是积性函数有显然的好处,能够加速处理出函数值。
当我们需要处理 f ( n ) f(n) 的时候,如果直接求会出现效率问题,求出 n = i = 1 t p i k i n=\prod_{i=1}^tp_i^{k_i} ,则 f ( n ) = i = 1 t f ( p i k i ) f(n)=\prod_{i=1}^tf(p_i^{k_i})

在接下来的讨论中,所有函数不考虑恒等于 0 0 的常数函数。

一些记号:

  1. 定义函数的逐点加法和逐点乘法:
    ( f × g ) ( n ) = f ( n ) × g ( n ) (f\times g)(n)=f(n)\times g(n)

( f + g ) ( n ) = f ( n ) + g ( n ) (f+g)(n)=f(n)+g(n)
接下来有一部分可能会因为笔者的sb错误将 × \times \cdot 混用,不过不会引起歧义。
2. [ a ] [a] ,条件求值表达式,当表达式 a a 为真的时候, [ a ] [a] 值为 1 1 ,否则值为 0 0

1.Dirichlet卷积

定义两个数论函数的Dirichlet卷积为:

( f g ) ( n ) = d n f ( d ) g ( n d ) (f*g)(n)=\sum_{d\mid n}f(d)g(\frac{n}d)

Dirichlet卷积的性质:

  1. 交换律: f g = g f f*g=g*f
  2. 结合律: ( f g ) h = f ( g h ) (f*g)*h=f*(g*h)
  3. 分配率: f ( g + h ) = f g + f h f*(g+h)=f*g+f*h
  4. 单位元: f ϵ = ϵ f = f f*\epsilon=\epsilon*f=f ,其中 ϵ ( n ) = [ n = 1 ] \epsilon(n)=[n=1] ,是完全及性函数
  5. 对于两个积性函数 f , g f,g ,它们的 D i r i c h l e t Dirichlet 卷积也是积性函数

2.常见积性函数

  1. 除数函数: n n 的约数的 k k 次幂之和, σ k ( n ) = d n d k \sigma_k(n)=\sum_{d\mid n}d^k
  2. 约数个数函数: n n 的约数个数, d ( n ) = σ 0 ( n ) = d n 1 d(n)=\sigma_0(n)=\sum_{d\mid n}1
  3. 约数和函数: n n 的所有约数之和, σ ( n ) = σ 1 ( n ) = d n d \sigma(n)=\sigma_1(n)=\sum_{d\mid n}d
  4. 欧拉函数: [ 1 , n ] [1,n] 中与 n n 互质的数的个数, ϕ ( n ) = φ ( n ) = i = 1 n [ g c d ( i , n ) = 1 ] \phi(n)=\varphi(n)=\sum_{i=1}^n[gcd(i,n)=1]
  5. 莫比乌斯函数:若 n n 有平方因子,则 μ ( n ) = 0 \mu(n)=0 ,否则,假设 n n k k 个不同的质数相乘得到, μ ( n ) = ( 1 ) k \mu(n)=(-1)^k ,实际上,莫比乌斯函数的定义式为 d n μ ( d ) = [ n = 1 ] \sum_{d\mid n}\mu(d)=[n=1]
  6. 元函数: ϵ ( n ) = [ n = 1 ] \epsilon(n)=[n=1] ,完全积性。
  7. 幂函数: I d k ( n ) = n k Id_k(n)=n^k ,完全积性。
  8. 恒等函数: I ( n ) = I d 0 ( n ) = 1 I(n)=Id_0(n)=1 ,完全积性。
  9. 单位函数: I d ( n ) = I d 1 ( n ) = n Id(n)=Id_1(n)=n ,完全积性。

2.1 一些Dirichlet卷积恒等式

d ( n ) = d n 1 , d = I I σ ( n ) = d n d , σ = I I d ϵ ( n ) = d n μ ( d ) , ϵ = μ I ϕ ( n ) = d n μ ( d ) n d , ϕ = μ I d I d ( n ) = d n ϕ ( d ) , I d = ϕ I \begin{aligned} &d(n)=\sum_{d\mid n}1,&&即d=I*I\\ &\sigma(n)=\sum_{d\mid n}d,&&即\sigma=I*Id\\ &\epsilon(n)=\sum_{d\mid n}\mu(d),&&即\epsilon=\mu*I\\ &\phi(n)=\sum_{d\mid n}\mu(d)\frac{n}d,&&即\phi=\mu*Id\\ &Id(n)=\sum_{d\mid n}\phi(d),&&即Id=\phi*I \end{aligned}

在接下来的部分中,将会证明后两个式子(前三个直接由定义得到)。

不过现在先看一个东西: g ( n ) = d n f ( d ) g(n)=\sum_{d\mid n}f(d) ,即 g = f I g=f*I ,我们有结论:如果 g g 是积性函数,则 f f 必然是积性函数(由Dirichlet卷积的性质不难发现 f f 是积性函数的时候 g g 一定是积性函数)。

证明: n = m 1 m 2 g c d ( m 1 , m 2 ) = 1 n=m_1m_2,gcd(m_1,m_2)=1

由于 g g 是积性函数,则 g ( m 1 m 2 ) = g ( m 1 ) g ( m 2 ) g(m_1m_2)=g(m_1)g(m_2)

展开左边得到:

g ( m 1 m 2 ) = d m 1 m 2 f ( d ) = d 1 m 1 d 2 m 2 f ( d 1 d 2 ) g(m_1m_2)=\sum_{d\mid m_1m_2}f(d)=\sum_{d_1\mid m_1}\sum_{d_2\mid m_2}f(d_1d_2)

展开右边得到:

g ( m 1 ) g ( m 2 ) = d 1 m 1 d 2 m 2 f ( d 1 ) f ( d 2 ) g(m_1)g(m_2)=\sum_{d_1\mid m_1}\sum_{d_2\mid m_2}f(d_1)f(d_2)

所以得到:

d 1 m 1 d 2 m 2 f ( d 1 d 2 ) = d 1 m 1 d 2 m 2 f ( d 1 ) f ( d 2 ) \sum_{d_1\mid m_1}\sum_{d_2\mid m_2}f(d_1d_2)=\sum_{d_1\mid m_1}\sum_{d_2\mid m_2}f(d_1)f(d_2)

由恒等式的性质可以得到 f ( d 1 d 2 ) = f ( d 1 ) f ( d 2 ) f(d_1d_2)=f(d_1)f(d_2) 。所以 f f 是积性函数。

当然,我们也可以构造另一个 g ( n 2 ) g(n_2) ,展开后相减也能得到上述结论。

3. 一些积性函数的特性

该部分默认 n n 的唯一分解式为: n = i = 1 t p i k i n=\prod_{i=1}^tp_i^{k_i} ,且空的 \prod 默认值为 1 1

3.1 欧拉函数

1. ϕ ( n ) = i = 1 t ( p i 1 ) p i k i 1 = n i = 1 t ( 1 1 p i ) \phi(n)=\prod_{i=1}^{t}(p_i-1)p_i^{k_i-1}=n\prod_{i=1}^{t}(1-\frac{1}{p_i})

2.降幂大法:欧拉定理和广义欧拉定理

3. I d ( n ) = d n ϕ ( d ) Id(n)=\sum_{d\mid n}\phi(d) ,即 I d = ϕ I Id=\phi*I

证明: 显然欧拉函数 ϕ ( n ) \phi(n) 可以代表一个东西——分母为 n n 的最简真分数个数,特别的, ϕ ( 1 ) = 1 \phi(1)=1 表示 1 1 \frac{1}{1} 这个分数。

1 n , 2 n , 3 n , n n \frac{1}n,\frac{2}{n},\frac{3}n,…\frac{n}n ,排成一列,化简后以分母分类统计即可得到上式。

4. ϕ ( n ) = d n μ ( d ) n d \phi(n)=\sum_{d\mid n}\mu(d)\frac{n}d ,即 ϕ = μ I d \phi=\mu*Id

首先由刚才证明的结论 I d = ϕ I Id=\phi*I ,两边卷上一个 μ \mu 得到 I d μ = ϕ I μ Id*\mu=\phi*I*\mu

由结合律得到 ϕ I μ = ϕ ( I μ ) = ϕ ϵ = ϕ \phi*I*\mu=\phi*(I*\mu)=\phi*\epsilon=\phi 。直接得到上式。

5. i = 1 n [ ( n , i ) = 1 ] i = n φ ( n ) + [ n = 1 ] 2 \sum_{i=1}^{n}{[(n,i)=1]\cdot i}=\frac{n\cdot\varphi(n)+[n=1]}{2}

显然,因为 g c d ( i , n ) = g c d ( n i , n ) gcd(i,n)=gcd(n-i,n)

6. ϕ ( i j ) = ϕ ( i ) ϕ ( j ) g c d ( i , j ) ϕ ( g c d ( i , j ) ) \phi(ij)=\frac{\phi(i)\phi(j)gcd(i,j)}{\phi(gcd(i,j))}

由于欧拉函数是积性函数,我们考虑这个结论对于质数的整数次幂分别是否成立。

首先如果 i = 1 i=1 j = 1 j=1 上式显然成立。

i = p s , j = p t i=p^s,j=p^t

ϕ ( i ) ϕ ( j ) g c d ( i , j ) ϕ ( g c d ( i , j ) ) = ( p 1 ) 2 p s + t + min ( s , t ) 2 ( p 1 ) p min ( s , t ) 1 = ( p 1 ) p s + t 1 = ϕ ( p s + t ) = ϕ ( i j ) \begin{aligned} &&&\frac{\phi(i)\phi(j)gcd(i,j)}{\phi(gcd(i,j))}\\ &=&&\frac{(p-1)^2p^{s+t+\min(s,t)-2}}{(p-1)p^{\min(s,t)-1}}\\ &=&&(p-1)p^{s+t-1}\\ &=&&\phi(p^{s+t})=\phi(ij) \end{aligned}

3.2 莫比乌斯函数

1. d n μ ( d ) = [ n = 1 ] \sum_{d\mid n}\mu(d)=[n=1] ,定义式。

2. μ ( n ) = { 0 n ( 1 ) t o t h e r w i s e \mu(n)=\left\{\begin{aligned}&0 &&n有平方因子\\&(-1)^t&&otherwise\end{aligned}\right.

这个才是莫比乌斯函数的性质,求解定义式里面的递归式解出来的。

首先我们都知道 ϵ ( n ) = [ n = 1 ] \epsilon(n)=[n=1] 是一个积性函数,由2.1中推出的结论,我们知道 μ ( n ) \mu(n) 也是一个积性函数。所以我们只需要考虑 μ ( p k ) \mu(p^k) 的情况就行了。

显然现在有 μ ( 1 ) = 1 \mu(1)=1 以及

μ ( 1 ) + μ ( p ) + μ ( p 2 ) + + μ ( p k ) = 0 \mu(1)+\mu(p)+\mu(p^2)+…+\mu(p^k)=0

对于 k 1 \forall k\geq 1 ,上式成立。

则我们能够推出 μ ( p ) = 1 \mu(p)=-1 k 2 , μ ( p k ) = 0 \forall k\geq 2,\mu(p^k)=0

然后由 μ \mu 的积性可以推出上面的通式。

P.S.:其实也可以把 2 2 当做定义式,来推 1 1 的结论(毕竟 2 2 要好记一些)。

现在把 2 2 当做定义式,对 1 1 进行证明

证明: d n μ ( d ) = [ n = 1 ] \sum_{d\mid n}\mu(d)=[n=1]

n = 1 n=1 的时候,上式显然成立。

n ̸ = 1 n\not=1 的时候,考虑非平方因子中不同的质因子个数,显然可以得到:
d n μ ( d ) = i = 1 t ( 1 ) i C t i = ( 1 1 ) k = 0 \begin{aligned} \sum_{d\mid n}\mu(d)&=&&\sum_{i=1}^t(-1)^iC_t^i\\ &=&&(1-1)^k\\ &=&&0 \end{aligned}

Q . E . D \mathscr{Q.E.D}

3.3 约数个数函数

重要的就一个性质,这个性质第一次是在毒瘤至极的 S D O I SDOI 反演题里面出现的。众所周知, S D O I SDOI 反演题 踩爆NOI 年年毒瘤。

d ( i j ) = k i l j [ g c d ( k , l ) = 1 ] d(ij)=\sum_{k\mid i}\sum_{l\mid j}[gcd(k,l)=1]

证明: 其实就是考虑怎么建立二元组 ( k , l ) (k,l) i j ij 的约数之间的一一对应的关系

首先每一个 i j ij 约数 q q ,考虑其中的某个质因子 p p ,使得 q = s p t q=s\cdot p^t

考虑 i = i p a , j = j p b i=i'\cdot p^a,j=j'\cdot p^b ,显然有 t a + b t\leq a+b

那么对于每个质因子 p p ,如果 t a t\leq a ,那么我们在 k k 当中分配 t t 个质因子,不给 l l 质因子 p p ,否则我们在 k k 当中不保存质因子 p p ,给 l l 分配 t a t-a 个质因子,显然,这样的分配下来 gcd ( k , l ) = 1 \gcd(k,l)=1 ,并且每一个质因子对应唯一的分解方案,由此就构成了一一对应。


五,莫比乌斯反演

1.前缀和的反演形式

如果两个数论函数 f , g f,g 满足:

g ( n ) = d n f ( d ) g(n)=\sum_{d\mid n}f(d)

则我们有:

f ( n ) = d n μ ( d ) g ( n d ) f(n)=\sum_{d\mid n}\mu(d)g(\frac{n}d)

其实从Dirichlet卷积上来还是很好理解的。

g = f I g=f*I

g μ = f I μ g*\mu=f*I*\mu

f ϵ = f = g μ f*\epsilon=f=g*\mu

不考虑Dirichlet卷积,来严格证明一遍。

证明:

d n μ ( d ) g ( n d ) = d n μ ( n d ) g ( d ) = d n μ ( n d ) t d f ( t ) = t n d n t μ ( n t d ) f ( t ) = t n d n t μ ( d ) f ( t ) = t n [ n t = 1 ] f ( t ) = f ( n ) \begin{aligned} \sum_{d\mid n}\mu(d)g(\frac{n}d)&=&&\sum_{d\mid n}\mu(\frac{n}d)g(d)\\ &=&&\sum_{d\mid n}\mu(\frac{n}d)\sum_{t\mid d}f(t)\\ &=&&\sum_{t\mid n}\sum_{d\mid \frac{n}t}\mu(\frac{n}{td})f(t)\\ &=&&\sum_{t\mid n}\sum_{d\mid\frac{n}t}\mu(d)f(t)\\ &=&&\sum_{t\mid n}[\frac{n}t=1]f(t)\\ &=&&f(n) \end{aligned}

2.后缀和的反演形式

其实后缀和也能够反演。

g ( n ) = n d f ( d )       f ( n ) = n d μ ( d n ) g ( d ) g(n)=\sum_{n\mid d}f(d)\iff f(n)=\sum_{n\mid d}\mu(\frac{d}n)g(d)

这个与Dirichlet卷积没有什么关系了,直接暴力证明。

证明:
n d μ ( d n ) g ( d ) = n d μ ( d n ) d t f ( t ) = n t f ( t ) d t n μ ( d ) = n t f ( t ) [ t n = 1 ] = f ( n ) \begin{aligned} \sum_{n\mid d}\mu(\frac{d}n)g(d)&=&&\sum_{n\mid d}\mu(\frac{d}n)\sum_{d\mid t}f(t)\\ &=&&\sum_{n\mid t}f(t)\sum_{d\mid\frac t n}\mu(d)\\ &=&&\sum_{n\mid t}f(t)[\frac{t}n=1]\\ &=&&f(n) \end{aligned}

3.扩展到实数域上的反演形式

特别的,如果函数 f , g f,g 在实数域上有定义的话,我们有如下的反演法则:
g ( n ) = d N + f ( n d )       f ( n ) = d N + μ ( d ) g ( n d ) g(n)=\sum_{d\in \mathbb{N^+}}f(\frac{n}d)\iff f(n)=\sum_{d\in \mathbb{N^+}}\mu(d)g(\frac{n}d)

证明:
d N + μ ( d ) g ( n d ) = d N + μ ( d ) t N + f ( n d t ) = T N + f ( n T ) d T μ ( d ) = T N + f ( n T ) [ T = 1 ] = f ( n ) \begin{aligned} \sum_{d\in\mathbb{N^+}}\mu(d)g(\frac{n}d)&=&&\sum_{d\in\mathbb{N^+}}\mu(d)\sum_{t\in\mathbb{N^+}}f(\frac{n}{dt})\\ &=&&\sum_{T\in\mathbb{N^+}}f(\frac{n}T)\sum_{d\mid T}\mu(d)\\ &=&&\sum_{T\in \mathbb{N^+}}f(\frac{n}T)[T=1]\\ &=&&f(n) \end{aligned}

然而又没有题。。。

4.对莫比乌斯反演的深刻认识

其实在积性函数部分我们已经认识了一个莫比乌斯反演式子:

I d = ϕ I ϕ = I d μ Id=\phi*I\\\phi=Id*\mu

具体来说就是这个: ϕ ( n ) = d n μ ( d ) n d \phi(n)=\sum_{d\mid n}\mu(d)\frac{n}d

现在考虑用容斥原理的方式来理解这个式子。

考虑一个正整数 k n k\leq n ,我们要计算有多少个 k k n n 互质。

d = g c d ( n , k ) d=gcd(n,k) ,当 d > 1 d > 1 时,所有这样的 k k 都需要被除去。

对于所有 p d p\mid d 的情况,都需要除去一次,就是 n p -\frac{n}p

对于所有 p 1 p 2 d p_1p_2\mid d 的情况,在第一步中被重复除去了两次,要加回来一次,就是 n p 1 p 2 \frac{n}{p_1p_2}

对于所有 p 1 p 2 p 3 d p_1p_2p_3\mid d 的情况,在第一步中被去掉了三次,在第二步中被加回来三次,所以要再去掉一次,就是 n p 1 p 2 p 3 -\frac{n}{p_1p_2p_3}

……以此类推

对于所有 p 1 p 2 . . . p t d p_1p_2...p_t\mid d 的情况,我们需要加上 ( 1 ) t n p 1 p 2 p t (-1)^t\frac{n}{p_1p_2…p_t}

实际上这里 μ \mu 的作用就是提供了前面的容斥系数,换句话说,莫比乌斯反演的本质就是容斥原理。

前缀和的反演形式为例:

g ( n ) = d n f ( d )       f ( n ) = d n μ ( d ) g ( n d ) g(n)=\sum_{d\mid n}f(d)\iff f(n)=\sum_{d\mid n}\mu(d)g(\frac{n}d)

考虑对 d d 分情况讨论(不考虑 μ ( d ) = 0 \mu(d)=0 )。

d = 1 d=1 时,所有因子全部算了一遍。

d d 有一个质因子的时候,所有 d t d\mid t 的都被减去了一遍。

d d 有两个质因子的时候,原来被减去的又加回来了。

……以此类推。。。(不想写了)(摔)

5.反演后的计算——整除分块

一般来说,推出反演式子的主要优化就是通过整除将很多需要计算的部分合到一起了,所以除了调和级数复杂度的容斥之外,一般也会用到整除分块来进行计算。

由于有很多 n i \lfloor\frac{n}i\rfloor 是相等的,这些我们可以放在一起计算,这就是整除分块的核心思想。

而所有不同的 n i \lfloor\frac{n}i\rfloor 最多只有 O ( n ) O(\sqrt n) 种。

考虑当 i n i\leq \sqrt n 的时候,显然只有不超过 n \sqrt n 种不同的取值,当 i > n i > \sqrt n 的时候, n i n \lfloor\frac{n}i\rfloor\leq \sqrt n 也只有不超过 n \sqrt n 种不同的取值,所以不同值的规模不超过 O ( n ) O(\sqrt n)

所以怎么分块,对于一个值 n i \lfloor\frac{n}i\rfloor ,要得到它的 i i 的值需要满足 i [ n n i + 1 + 1 , n n i ] i\in [\Big\lfloor\frac{n}{\lfloor\frac{n}{i}\rfloor+1}\Big\rfloor+1,\Big\lfloor\frac{n}{\lfloor\frac{n}i\rfloor}\Big\rfloor]

所以整除分块一般都是这样写:

for(int i=1,j;i<=n;i=j+1){
	j=n/(n/i);
	...
}

考虑证明这个区间的上界是上确界(就是不能再扩展了),下界就显然了(上一个区间的上界+1)。

首先设 n ÷ i = k . . . s n\div i=k...s ,那么根据带余除法的定义,显然 n i = k \lfloor\frac{n}{i}\rfloor=k

接下来分类讨论:

1. i n i\leq \sqrt n

首先我们证明这个时候所有的 n i \lfloor\frac{n}i\rfloor 值都不相同,即证明 n i ̸ = n i + 1 \lfloor\frac{n}i\rfloor\not=\lfloor\frac{n}{i+1}\rfloor

由于 i n i\leq \sqrt n ,显然有 k n i &gt; s k\geq \sqrt n\geq i &gt; s

考虑反证,假设 n i = n i + 1 \lfloor\frac{n}i\rfloor=\lfloor\frac{n}{i+1}\rfloor

此时设 p = n k ( i + 1 ) p=n-k(i+1) ,显然我们需要 0 p i + 1 0\leq p \leq i+1

利用 n n 建立等式:

n = i k + s + ( k s + p ) = n + ( k s + p ) n=ik+s+(k-s+p)=n+(k-s+p)

而我们有 k s &gt; 0 , p 0 k-s&gt; 0,p\geq 0 ,显然这里就出现了矛盾,假设恒不成立,原命题得证。

那么我们在这一段只需要证明 n n i = i \lfloor\frac{n}{\lfloor\frac{n}i\rfloor}\rfloor=i 就行了。

接下来尝试证明 n k = i \lfloor\frac{n}k\rfloor=i

n ÷ k = ( i + t ) . . . w n\div k=(i+t)...w

再次利用 n n 构建等式:

n = k ( i + t ) + w = i k + s n=k(i+t)+w=ik+s

k t + w = s kt+w=s

因为 s &lt; i n s&lt;i\leq \sqrt n k n k\geq \sqrt n ,而且 k , t , w , s 0 k,t,w,s\geq 0

所以上式成立当且仅当 t = 0 , w = s t=0,w=s

所以 n n i = n k = i \lfloor\frac{n}{\lfloor\frac{n}i\rfloor}\rfloor=\lfloor\frac{n}k\rfloor=i ,得证。

2. i &gt; n i&gt;\sqrt n

显然这时候有 k n k\leq \sqrt n ,这时候我们要证明的就是 n n i = n k = i m a x \lfloor\frac{n}{\lfloor\frac{n}{i}\rfloor}\rfloor=\lfloor\frac{n}k\rfloor=i_{max}

由于除法运算的单调性, i m a x i_{max} 显然有如下性质: n i m a x = n i = k \lfloor\frac{n}{i_{max}}\rfloor=\lfloor\frac{n}{i}\rfloor=k n i m a x + 1 ̸ = n i = k \lfloor\frac{n}{i_{max}+1}\rfloor\not=\lfloor\frac{n}i\rfloor=k

也就是说我们要证明: n n k = k , n n k + 1 = ̸ k \lfloor\frac{n}{\lfloor\frac{n}{k}\rfloor}\rfloor=k,\lfloor\frac{n}{\lfloor\frac{n}{k}\rfloor+1}\rfloor =\not k

好的这个刚才已经证明过了。

为什么? k n k\leq \sqrt n ,刚才 i n i\leq \sqrt n 的部分是不是已经证明过了?

Q . E . D \mathscr{Q.E.D}


六,筛法

筛法一般指的就是埃氏筛,不过此处用筛法指代一种较为通用的求积性函数前缀和或点值的方法,一般来说也能筛质数就对了。(当然,能求前缀和当然就能直接求点值了)

1.埃氏筛

最古老的筛法,能够在 Θ ( n ln ln n ) \Theta(n\ln\ln n) 复杂度内处理 n n 以内的数的积性函数值,(假设在知道某个质因子之后可以 O ( 1 ) O(1) 转移)。

核心思想是利用质因子来将需要计算的函数值

我们只需要确定每个质因子的影响就行了。

比如筛欧拉函数: ϕ ( n ) = n i = 1 t p i 1 p i \phi(n)=n\prod_{i=1}^t{\frac{p_i-1}{p_i}}

所以我们只需要在用每个质因子筛的时候乘除一下就行了。

不过埃氏筛最大的用处不是这个,一般埃氏筛都是用来筛一个区间 [ L , R ] [L,R] 内有哪些质数,这个复杂度就十分优秀。只需要用 R \sqrt R 以内的所有质数筛一下就行了。

2.欧拉筛

欧拉筛法,用于求 n n 以内的函数的

又称线性筛,一般认为其复杂度为线性 Θ ( n ) \Theta(n) ,不过也有地方认为是 Θ ( n 1 + ϵ ) \Theta(n^{1+\epsilon}) ,其中 ϵ \epsilon 是一个非常小的正数。

核心思想是利用最小质因子将合数筛出,同时利用积性简化求函数点值的过程。

利用欧拉筛法求积性函数点值,只需要考虑两点:

  1. f ( p ) f(p) ,其中 p P p\in \mathbb{P}
  2. f ( n p ) f(np) ,其中 p p n n 的最小质因子

其他的都可以直接上积性处理。

比如筛欧拉函数,只需要两点:

  1. ϕ ( p ) = p 1 \phi(p)=p-1
  2. ϕ ( n p ) = ϕ ( n ) p \phi(np)=\phi(n)p

其他函数同理。

3.杜教筛

有的时候,我们需要求一些积性函数的前缀和,而且需要在低于线性时间复杂度内完成。

考虑求积性函数 f ( n ) f(n) 的前缀和 S ( n ) S(n)

S ( n ) = i = 1 n f ( i ) S(n)=\sum_{i=1}^nf(i)

首先我们可以线性筛,然后求出前缀和。

n = 1 e 11 n=1e11 ?

这就是高级筛法需要做的事——在低于线性时间复杂度内求出积性函数前缀和。

接下来是杜教筛时间:

我们找到另一个函数 g g ,把 f g f*g 的前缀和求一下得到:

i = 1 n ( f g ) ( i ) = i = 1 n d i f ( i d ) g ( d ) = d = 1 n g ( d ) i = 1 n d f ( i ) = d = 1 n g ( d ) S ( n d ) \begin{aligned} \sum_{i=1}^n(f*g)(i)&amp;=&amp;&amp;\sum_{i=1}^n\sum_{d\mid i}f(\frac{i}d)g(d)\\ &amp;=&amp;&amp;\sum_{d=1}^{n}g(d)\sum_{i=1}^{\lfloor\frac{n}d\rfloor}f(i)\\ &amp;=&amp;&amp;\sum_{d=1}^ng(d)S(\lfloor\frac{n}{d}\rfloor) \end{aligned}

我们把我们需要的东西单独拎出来: g ( 1 ) S ( n ) g(1)S(n) ,那么现在就有一个递归式:

g ( 1 ) S ( n ) = i = 1 n ( f g ) ( i ) i = 2 n g ( i ) S ( n i ) g(1)S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(i)S(\lfloor\frac{n}i\rfloor)

然后我们就可以愉快的整除分块+递归+记忆化来求出这个式子了。

算一算复杂度:

T ( n ) = O ( n ) + i = 1 n T ( i ) + T ( n i ) T(n)=O(\sqrt{n})+\sum_{i=1}^{\sqrt{n}}{T(i)+T(\frac{n}{i})}

只需要展开一层就行了,后面的都是高阶小量。

于是得到

T ( n ) = i = 1 n O ( i ) + O ( n i ) = O ( n 3 4 ) T(n)=\sum_{i=1}^{\sqrt n}O(\sqrt i)+O(\sqrt\frac{n}i)=O(n^{\frac{3}4})

化求和为积分,即可得到上面的等式。

实际上可以优化。由于 f f 是积性函数,我们考虑用线性筛预处理前 m m 个值,则:

T ( n ) = i = 1 n m O ( n i ) + O ( m ) = O ( n m + O ( m ) ) T(n)=\sum_{i=1}^{\lfloor\frac{n}m\rfloor}O(\sqrt\frac{n}i)+O(m)=O(\sqrt{\frac{n}m}+O(m))

解出来得到 m = O ( n 2 3 ) m=O(n^{\frac{2}3}) 的时候最优,此时 T ( n ) = O ( n 2 3 ) T(n)=O(n^{\frac{2}3})

当然这是单个询问的做法,多组数据还是尽量能多预处理就多预处理吧。

4.讲了估计也没人听的扩展埃氏筛

用于求积性函数前缀和。

用埃氏筛处理了质数,复杂度为 O ( n 0.7 ) O(n^{0.7}) (踩爆洲阁筛)

可以用于杜教筛题目骗分

咕了

5.过于毒瘤一般用于劝退的min_25和洲阁

同样用于求积性函数前缀和,洲阁筛复杂度 O ( n 3 4 / log n ) O(n^{\frac{3}{4}}/\log n) ,min_25复杂度 O ( n 3 4 / log n ) O(n^{\frac{3}4}/\log n) ,常数优于洲阁筛,实际速度应该和扩埃差不多。

同样可以用于杜教筛骗分

咕了


七,组合数取模

最后,我们来看一个古老的问题——组合数取模。

就是求: ( n m ) % M {n\choose m }\%M

接下来从两种不同的角度来对这个问题进行分析。

1.枚举法

并非万能,但是在某些情况十分通用且高效的方法。

这一部分主要是从组合数 C n m C_n^m 的基本性质入手分析,从而得到取模结果的。

由于 C n m = C n n m C_n^m=C_n^{n-m} ,所以我们只考虑 m n / 2 m\leq n/2 的情况。

1.1 直接枚举

如果 1 , 2 , m 1,2,…,m % M o d \%Mod 意义下全部存在逆元,可以直接计算分子分母模意义下的值,然后扩欧求逆元即可,注意不要求 M o d Mod 是质数。

M o d Mod 是质数的时候,也可以线性递推预处理出所有逆元及阶乘逆元,即可 O ( n ) O(n) 处理, O ( 1 ) O(1) 回答,实际中一般也是利用这种方法。

1.2 预处理

由于

( n m ) = ( n 1 m ) + ( n 1 m 1 ) {n\choose m}={n-1\choose m}+{n-1\choose m-1}

所以我们可以 O ( n m ) O(nm) 进行一次预处理,不用担心逆元的问题,因为这里只有加法。

适用于复杂度允许且需要多次询问组合数的情况。

注意,当 n n 远大于 m m 时,可能不如第一种方法优秀。

1.3 质因数分解

1 , 2 , , m 1,2,⋯,m 中存在数字在模 p p 意义下没有逆元,则无法使用相应的除法,而是在运算前将分子和分母中的相关项相消,一个显而易见的想法便是将分子和分母所对应的阶乘化为质因数分解式,在质因数的幂次上进行加减法,然后利用快速幂计算出我们需要的答案。

实际上只需要用线性筛筛出每个数最小或最大质因子,从后往前递推即可。只在质数的时候计算快速幂即可加速。

2.Lucas定理及其扩展

这一部分主要介绍Lucas定理以及扩展Lucas定理在解决组合数取模时的作用。

2.1 Lucas定理

Lucas定理适用于模数为质数的情况,内容描述如下:

( n m ) ( n % p m % p ) ( n p m p ) ( m o d p ) {n\choose m}\equiv {n\%p\choose m\%p}\cdot {\lfloor\frac{n}p\rfloor\choose\lfloor\frac{m}{p}\rfloor}\pmod p

证明:

首先,显然有:

( p i ) 0 ( m o d p ) , 0 &lt; i &lt; p {p\choose i}\equiv 0\pmod p,0 &lt; i &lt; p

那么由二项式定理:

( 1 + x ) p 1 + x p ( m o d p ) (1+x)^p\equiv 1+x^p\pmod p

n = s p + q , q = n % p , s = n p n=sp+q,q=n\%p,s=\lfloor\frac{n}p\rfloor ,由二项式定理,则

( 1 + x ) n ( 1 + x ) s p ( 1 + x ) q ( m o d p ) ( ( 1 + x ) p ) s ( 1 + x ) q ( m o d p ) ( 1 + x p ) s ( 1 + x ) q ( m o d p ) i = 0 s ( s i ) x i p j = 0 q ( q j ) x j ( m o d p ) \begin{aligned} (1+x)^n&amp;\equiv&amp;&amp;(1+x)^{sp}(1+x)^q &amp;\pmod p\\ &amp;\equiv &amp;&amp;((1+x)^p)^s (1+x)^q&amp;\pmod p\\ &amp;\equiv &amp;&amp;(1+x^p)^s(1+x)^q&amp;\pmod p\\ &amp;\equiv &amp;&amp;\sum_{i=0}^s{s\choose i}x^{ip}\sum_{j=0}^q{q\choose j}x^j&amp;\pmod p \end{aligned}

显然,对于每一个 x m x^m m m 都是由一前一后唯一相乘得到的,设 m = s p + q , q = m % p , s = m p m=s&#x27;p+q&#x27;,q&#x27;=m\%p,s&#x27;=\lfloor\frac{m}p\rfloor

考虑第 m m 项的系数,由二项式定理,它应该是 ( n m ) {n\choose m} ,由上面推出的同余式可以得到:

( s s ) ( q q ) ( n m ) ( m o d p ) {s\choose s&#x27;}{q\choose q&#x27;}\equiv {n\choose m}\pmod p

( n m ) ( n % p m % p ) ( n p m p ) ( m o d p ) {n\choose m}\equiv {n\%p\choose m\%p}\cdot {\lfloor\frac{n}p\rfloor\choose\lfloor\frac{m}{p}\rfloor}\pmod p

于是就可以 O ( p ) O(p) 预处理 1 1 p 1 p-1 的阶乘及阶乘逆元,然后 O ( log p n ) O(\log_pn) 回答每个询问了。

2.2 扩展Lucas定理

用于解决模数任意的情况,不过复杂度略大。

有的题扩展Lucas可做,但是不是最优。(不过作为一种骗分算法还是有必要了解一下)

显然CRT能够帮助我们将问题简化成如下形式:

( n m ) % p e {n\choose m}\%p^e

现在考虑求

n ! m ! ( n m ) ! % p e \frac{n!}{m!(n-m)!}\%p^e

考虑将阶乘表示成 p s q p^sq 的形式,其中 g c d ( q , p ) = 1 gcd(q,p)=1 ,那么就能够用 q q 的逆元和 p p 的次数来解决了。

说清楚一点,就是这样:
n ! = p s 1 q 1 , m ! = p s 2 q 2 , ( n m ) ! = p s 3 q 3 n!=p^{s_1}q_1,m!=p^{s_2}q_2,(n-m)!=p^{s_3}q_3 ,则:

( n m ) p s 1 s 2 s 3 q 1 q 2 1 q 3 1 ( m o d p e ) {n\choose m}\equiv p^{s_1-s_2-s_3}q_1q_2^{-1}q_3^{-1}\pmod {p^e}

现在考虑求 n ! % p e n!\%p^e

首先可以把被 p p 整除的数全部提取出来得到:
n ! n p ! p n p i = 1 , p i n i ( m o d p e ) n!\equiv \lfloor\frac{n}p\rfloor! p^{\lfloor\frac{n}p\rfloor}\prod_{i=1,p\nmid i}^ni\pmod {p^e}

前面 n p ! \lfloor\frac{n}p\rfloor! 可以直接递归处理。

后面我们发现 % p e \%p^e 是几个相同的序列和最后的一个冗余序列。

所以我们只需要求

i = 1 , p i p e i % p e \prod_{i=1,p\nmid i}^{p^e}i\%p^e

其实这里可以优化,没必要暴力 O ( p e ) O(p^e) 处理(不过这个优化也只能算是常数,而不是复杂度)。

我们有威尔逊定理,对于 p P ( p 1 ) ! 1 ( m o d p ) p\in \mathbb{P}\Leftrightarrow (p-1)!\equiv -1\pmod p
证明:
p = 2 , 3 p=2,3 的情况是平凡的。所以接下来令 p 5 p\geq 5

由逆元的存在性以及逆元存在即唯一的性质即可证明。

由二次探测定理,在 % p \%p 意义下逆元为自身的只有 1 , p 1 1,p-1 ,其他 p 3 p-3 个数总能两两配对使得乘积为 1 1 ,所以

( p 1 ) ! 1 × ( p 1 ) 1 ( m o d p ) (p-1)!\equiv 1\times (p-1)\equiv -1\pmod p

然后将其泛化为高斯定理,下面的 p p 是奇质数:

i = 1 , g c d ( i , m ) = 1 m i { 1 ( m o d m ) m = 4 , p α , 2 p α 1 ( m o d m ) o t h e r w i s e \prod_{i=1,gcd(i,m)=1}^mi\equiv \left\{\begin{aligned}-1\pmod m &amp;&amp;m=4,p^{\alpha},2p^{\alpha}\\1 \pmod m &amp;&amp;otherwise\end{aligned}\right.

证明: 咕了 你们自己用原根倒一倒吧。注意 m = 2 m=2 平凡的情况。

这就是所谓常数级别的优化。


题目板块

以下所有代码包含此默认头文件(有不对的地方请自行调整)
有需要调成long long 的地方自行调整。

#include<bits/stdc++.h>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define pc put_char
#define puts put_s
#define cs const 

namespace IO{
	cs int Rlen=1<<18|1;
	inline char get_char(){
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	char obuf[Rlen],*p3=obuf;
	inline char put_char(char c){
		*p3++=c;
		if(p3==obuf+Rlen)fwrite(obuf,1,Rlen,stdout),p3=obuf;
	}
	inline void put_s(cs char *s){
        for(int re i=0;s[i];++i)pc(s[i]);pc('\n');
    }
	
	struct IO{
		~IO(){fwrite(obuf,1,p3-obuf,stdout);}
	}io;
	
	inline int getint(){
		re char c;
		re bool f=0;
		while(!isdigit(c=gc()))if(c=='-')f=1;re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return f?-num:num;
	}
	inline void outint(int a){
		static char ch[23];
		if(a<0)pc('-'),a=-a;
		if(a==0)pc('0');
		while(a)ch[++ch[0]]=a-a/10*10,a/=10;
		while(ch[0])pc(ch[ch[0]--]^48);
	}
	inline char getalpha(){
		re char c;
		while(!isalpha(c=gc()));return c;
	}
	inline char getsth(){
		re char c;
		while(isspace(c=gc()));return c;
	}
}
using namespace IO;

[POI2002][HAOI2007]反素数

传送门

解析:

考虑唯一分解 n = i = 1 p i k i n=\prod_{i=1}p_i^{k_i} ,注意此处的唯一分解考虑所有的质数,不存在该质因子的话默认对应项的 k i k_i 为0。

显然 d ( n ) = i = 1 ( k i + 1 ) d(n)=\prod_{i=1}{(k_i+1)}

我们发现反素数只有一个特点: k i k_i 单调不增,否则我们总是可以通过交换前后两个质数的指数来得到一个 d ( n ) = d ( m ) d(n)=d(m) m &lt; n m&lt;n

所以可以爆搜了,由于爆搜过程中的数的大小是指数级的增长,所以可以时间保证。

代码(把getint换成long long类型):

bool mark[50002];
int prime[50002],pcnt;

inline void linear_sieves(int len=50000){
	mark[1]=true;
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i;
		for(int re j=1;j<=pcnt&&i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
		}
	}
}

ll ans,maxn;
ll n;

inline void dfs(ll num,ll tot,int pos,int up){
	if(maxn<tot||(maxn==tot&&num<ans)){
		ans=num;
		maxn=tot;
	}
	if(pos>pcnt)return ;
	for(int re i=1;i<=up;++i){
		num*=prime[pos];
		if(num>n)return ;
		dfs(num,tot*(i+1),pos+1,i);
	}
}

signed main(){
	n=getint();
	linear_sieves(min(n,(ll)50000));
	dfs(1,1,1,500);
	cout<<ans<<endl;
	return 0;
}

[HDU2138]How many prime numbers

传送门
解析:
Miller-Rabin裸题。

代码:

inline ll mul(ll a,ll b,ll mod){
    return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}

inline ll quickpow(ll a,ll b,ll mod){
    re ll ans=1;
    for(;b;b>>=1,a=mul(a,a,mod))if(b&1)ans=mul(ans,a,mod);
    return ans;
}

cs int P=10000007;
bool mark[P];
int prime[P],pcnt;
inline void linear_sieves(int len=P-7){
    mark[1]=true;
    for(int re i=2;i<=len;++i){
        if(!mark[i])prime[++pcnt]=i;
        for(int re j=1;j<=pcnt&&i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j]==0)break;
        }
    }
}

inline bool isprime(ll x){
    if(x<=P-7)return !mark[x];
    if(!(x&1)||(x%3==0)||(x%5==0)||(x%7)==0)return false;
    ll t=x-1,s=0;
    while(!(t&1))t>>=1,++s;
    for(int re i=1;i<=14&&prime[i]<x;++i){
        ll num=quickpow(prime[i],t,x),pre=num;
        if(x%prime[i]==0)return false;
        for(int re j=0;j<s;++j){
            num=mul(num,num,x);
            if(num==1&&pre!=1&&pre!=x-1)return false;
            pre=num;
        }
        if(num!=1)return false;
    }
    return true;
}

int n;
signed main(){
    linear_sieves();
    while(~scanf("%d",&n)){
        int ans=0;
        while(n--)if(isprime(getint()))++ans;
        printf("%d\n",ans);
    }
    return 0;
}

[BZOJ3667]Rabin-Miller算法

传送门
解析:
Pollard-Rho裸题

代码(outint末尾加一个pc(’\n’)):

inline ll mul(ll a,ll b,ll mod){
    return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}

inline ll quickpow(ll a,ll b,ll mod){
    re ll ans=1;
    for(;b;b>>=1,a=mul(a,a,mod))if(b&1)ans=mul(ans,a,mod);
    return ans;
}

cs int P=1000007;
bool mark[P];
int prime[P],pcnt;
int maxpri[P];
inline void linear_sieves(int len=P-7){
    mark[1]=true;
    for(int re i=2;i<=len;++i){
        if(!mark[i])prime[++pcnt]=i,maxpri[i]=i;
        for(int re j=1;j<=pcnt&&i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            maxpri[i*prime[j]]=maxpri[i];
            if(i==i/prime[j]*prime[j])break;
        }
    }
}

inline bool isprime(ll x){
    if(x<=P-7)return !mark[x];
    if(!(x&1)||(x%3==0)||(x%5==0)||(x%7)==0||(x%61==0)||(x%24251)==0)return false;
    ll t=x-1,s=0;
    while(!(t&1))t>>=1,++s;
    for(int re i=1;i<=5;++i){
    	ll p=prime[rand()%pcnt+1]%x;
        ll num=quickpow(p,t,x),pre=num;
        if(x%p==0)return false;
        for(int re j=0;j<s;++j){
            num=mul(num,num,x);
            if(num==1&&pre!=1&&pre!=x-1)return false;
            pre=num;
        }
        if(num!=1)return false;
    }
    return true;
}

inline ll gcd(ll a,ll b){
    if(!a||!b)return a+b;
    re int t=__builtin_ctzll(a|b);
    a>>=__builtin_ctzll(a);
    while(b){
        b>>=__builtin_ctzll(b);
        if(a>b)a^=b,b^=a,a^=b;
        b-=a;
    }
    return a<<t;
}

ll ans;
inline ll Pollard_Rho(ll x){
    if(x%2==0)return 2;
    if(x%3==0)return 3;
    if(x%5==0)return 5;
    if(x%7==0)return 7;
    if(x%61==0)return 61;
    if(x%24251==0)return 24251;
    ll n=0,m=0,t=1,q=1,c=rand()%(x-1)+1;
    for(ll k=2;;k<<=1,m=n,q=1){
    	for(ll i=1;i<=k;++i){
    		n=(mul(n,n,x)+c)%x;
    		q=mul(q,abs(m-n),x);
        }
        t=gcd(x,q);if(t>1)return t;
    }
}

inline void sieves(ll x){
    if(x==1)return ;if(x<=ans)return ;
    if(isprime(x))return (void)(ans=max(ans,x));
    if(x<=P-7)return (void)(ans=max(ans,(ll)maxpri[x]));
    re ll p=x;
    while(p>=x)p=Pollard_Rho(p);
    sieves(p);
    while(x%p==0)x/=p;
    sieves(x);
}

int T;
signed main(){
    srand(time(0));
    linear_sieves();
    T=getint();
    while(T--){
        ll n=getint();
        if(n<=P-7)mark[n]?outint(maxpri[n]):puts("Prime");
        else {
            ans=0;
            sieves(n);
            if(ans==n)puts("Prime");
            else outint(ans);
        }
    }
    return 0;
}

[BZOJ4802]欧拉函数

传送门
解析:
由欧拉函数的性质: ϕ ( n ) = n i = 1 t p i 1 p i \phi(n)=n\prod_{i=1}^t\frac{p_i-1}{p_i} ,直接利用Pollard-Rho分解质因数,去重后计算即可。

代码就不放了。

[51nod1079]中国剩余定理

传送门
解析:
CRT板题(虽然较优的暴力也可以过)。

由于所有方程的模数都是质数,所以逆元可以直接用快速幂。

代码:

inline ll quickpow(ll a,ll b,ll mod){
	ll ans=1;
	for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;
	return ans;
}

inline ll inv(ll a,ll m){
	return quickpow(a,m-2,m);
}

cs int N=11;
ll mod[N],remain[N];
int n;
ll m=1;

inline int CRT(){
	for(int re i=1;i<=n;++i)m*=mod[i];
	ll ans=0;
	for(int re i=1;i<=n;++i){
		ans=(ans+(m/mod[i])*inv(m/mod[i],mod[i])%m*remain[i]%m)%m;
	}
	return ans;
}

signed main(){
	n=getint();
	for(int re i=1;i<=n;++i){
		mod[i]=getint();
		remain[i]=getint();
	}
	cout<<CRT();
	return 0;
} 

[WOJ4113]ZUA球困难综合征

传送门
解析:
发现模数 29393 = 7 13 17 19 29393=7*13*17*19 ,于是对于每个模数计算一下答案,最后用CRT合并就行了。

代码:

inline int quickpow(int a,int b,int mod,int ans=1){
	for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;
	return ans;
}

cs int M=29393;//7*13*17*19
cs int N=100005;
struct node{int remain[4][20];}t[N<<1],Ans;
cs int mod[4]={7,13,17,19};
int remain[4];
int inv[4][20];
inline int CRT(){
	int ans=0;
	for(int re i=0;i<4;++i)
	ans=(ans+(M/mod[i])*inv[i][(M/mod[i])%mod[i]]%M*remain[i]%M)%M;
	return ans;
}

inline int solve(char op,int val,int now,int mod){
	switch(op){
		case '+':return (val+now)%mod;
		case '*':return val*now%mod;
		case '^':return quickpow(now,val,mod);
	}
}

inline void pushup(int k){
	for(int re i=0;i<4;++i){
		for(int re j=0;j<mod[i];++j)
		t[k].remain[i][j]=t[k<<1|1].remain[i][t[k<<1].remain[i][j]]; 
	}
}

inline void build(int k,int l,int r){
	if(l==r){
		char op=getsth();
		int val=getint();
		for(int re i=0;i<4;++i){
			for(int re j=0;j<mod[i];++j)
			t[k].remain[i][j]=solve(op,val,j,mod[i]);
		}
		return ; 
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}

inline void update(int k,int l,int r,cs int &pos){
	if(l==r){
		char op=getsth();
		int val=getint();
		for(int re i=0;i<4;++i){
			for(int re j=0;j<mod[i];++j)
			t[k].remain[i][j]=solve(op,val,j,mod[i]);
		}
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)update(k<<1,l,mid,pos);
	else update(k<<1|1,mid+1,r,pos);
	pushup(k);
}

inline void solve(int x){
	for(int re i=0;i<4;++i)remain[i]=t[1].remain[i][x%mod[i]];
	outint(CRT()),pc('\n');
}

int n,m; 
signed main(){
	for(int re i=0;i<4;++i)inv[i][0]=inv[i][1]=1;
	for(int re j=0;j<4;++j){
		for(int re i=2;i<mod[j];++i){
			inv[j][i]=(mod[j]-mod[j]/i)*inv[j][mod[j]%i]%mod[j];
		}
	}
	
	n=getint();
	m=getint();
	build(1,1,n);
	while(m--){
		int op=getint(),x=getint();
		switch(op){
			case 1:{solve(x);break;}
			case 2:{update(1,1,n,x);break;}
		}
	}
	return 0;
}

[洛谷P4777]扩展中国剩余定理

传送门
解析:
exCRT板题

代码:

inline ll mul(ll a,ll b,ll mod){
	return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}

inline ll ex_gcd(ll a,ll b,ll &x,ll &y){
	if(!b){
		x=1,y=0;
		return a; 
	}
	ll t=ex_gcd(b,a%b,y,x);
	y-=a/b*x;
	return t;
}

cs int N=100005;
int n;
ll mod[N],a[N];

inline ll ex_CRT(){
	ll M=mod[1],ans=a[1];
	for(int re i=2;i<=n;++i){
		ll a1=ans,m1=M,a2=a[i],m2=mod[i];
		ll y1,y2,tmp=(a2-a1%m2+m2)%m2;
		ll g=ex_gcd(m1,m2,y1,y2);
		if(tmp%g)return -1;
		ans+=mul(y1,tmp/g,m2/g)*M;
		M*=m2/g;
		ans=(ans%M+M)%M;
	}
	return ans;
}

signed main(){
	n=getint();
	for(int re i=1;i<=n;++i)mod[i]=getint(),a[i]=getint();
	cout<<ex_CRT();
	return 0;
}

[NOI2018]屠龙勇士

传送门
解析:

首先按照这个题意,攻击每条龙用的剑的攻击力是确定的,设攻击第 i i 条龙用的剑攻击力为 C i C_i

剩下的部分看上去就像一个一元线性同余方程组。

实际上我们需要解的方程组中每个方程都是长成这个样子的 c i x a i ( m o d p i ) c_ix\equiv a_i\pmod {p_i}

但是发现一个问题,可能有 a i &gt; p i a_i &gt; p_i ,此时显然这个方程解出来的意义就会有一点问题。可能还没有把龙的血量砍到负数就已经同余了。

注意读题,我们发现特性1与 p i = 1 p_i=1 这个条件任何时候出现则仅出现一种。

换句话说我们可以大力特判掉这种情况,显然它的答案就是 max i = 1 n { a i c i } \max_{i=1}^n\{\lceil\frac{a_i}{c_i}\rceil\}

还有一种情况,即 i , a i = p i \forall i,a_i=p_i

这种时候,我们的同余方程解出来显然永远是 0 0 ,但是这是显然不对的。

不过我们发现,这时候能够杀死一条巨龙的刀数是倍数关系,所以全部求出来求个 l c m lcm 就行了。

所以我们要开始对同余方程化简了?不是,还有一种情况需要考虑。。。

一般来说,当 p i c i p_i\mid c_i 的时候,我们是杀不死这条巨龙的,但是当 a i = p i a_i=p_i 的时候,我们是随便砍任意正整数刀都能杀死巨龙的(因为现在能够保证解出来大于0)。

于是这个方程就废了。。。我们用随便一个毫无意义的 x 0 ( m o d 1 ) x\equiv 0\pmod 1 来代替一下就可以了。

现在考虑怎么将方程 c x a ( m o d p ) cx\equiv a\pmod p 化成 x a ( m o d p ) x\equiv a\pmod {p} 的形式。

同余方程的化简常规操作,从一般方程入手,先转成一般方程: c x + p y = a cx+py=a

解出一组特解 x 0 , y 0 x_0,y_0

则我们很容易得到通解的形式: x = x 0 + k l c m ( c , p ) x=x_0+klcm(c,p)

两边同时对 l c m ( c , p ) lcm(c,p) 取模,就可以直接得到我们需要的同余方程: x x 0 ( m o d l c m ( c , p ) ) x\equiv x_0\pmod {lcm(c,p)}

最终的方程组直接ex_CRT合并就行了。

代码:

inline ll mul(ll a,ll b,ll mod){
    return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}

inline ll ex_gcd(ll a,ll b,ll &x,ll &y){
    if(b==0){
        x=1;y=0;
        return a;
    }
    ll t=ex_gcd(b,a%b,y,x);
    y-=a/b*x;
    return t;
}

cs int N=100005;
ll a[N],mod[N],c[N];
int atk[N];
multiset<ll> s;
int n,m;

inline ll ex_CRT(){
    ll M=mod[1],ans=a[1];
    for(int re i=2;i<=n;++i){
        ll a1=ans,a2=a[i],m1=M,m2=mod[i];
        ll y1,y2,tmp=(a2-a1%m2+m2)%m2;
        ll g=ex_gcd(m1,m2,y1,y2);
        if(tmp%g)return -1;
        ans+=mul(y1,tmp/g,m2/g)*M;
        M*=m2/g;
        ans=(ans%M+M)%M;
    }
    return ans;
}

inline ll all_equal(){
    ll res=1;
    for(int re i=1;i<=n;++i){
        ll d=mod[i]/__gcd(mod[i],c[i]);
        res=res/__gcd(res,d)*d;
    }
    return res;
}

inline ll all_1(){
    ll res=0;
    for(int re i=1;i<=n;++i)res=max(res,(a[i]+c[i]-1)/c[i]);
    return res;
}

inline void solve(){
    n=getint(),m=getint();
    for(int re i=1;i<=n;++i)a[i]=getint();
    for(int re i=1;i<=n;++i)mod[i]=getint();
    for(int re i=1;i<=n;++i)atk[i]=getint();
    for(int re i=1;i<=m;++i)s.insert(getint());
    for(int re i=1;i<=n;++i){
        multiset<ll>::iterator it=(a[i]<*s.begin())?s.begin():(--s.upper_bound(a[i]));
        c[i]=*it;s.erase(it);s.insert(atk[i]);
    }
    for(int re i=1;i<=n;++i)
    if(mod[i]!=a[i])goto skip1;
    cout<<all_equal()<<"\n";return ;
    skip1:
    for(int re i=1;i<=n;++i)
    if(mod[i]!=1)goto skip2;
    cout<<all_1()<<"\n";return ;
    skip2:
    for(int re i=1;i<=n;++i){
        if(!(c[i]%=mod[i])){
            if(mod[i]==a[i])c[i]=1,mod[i]=1,a[i]=0;
            else {cout<<"-1\n";return ;}
        }
        ll y_1,y_2;
        ll g=ex_gcd(c[i],mod[i],y_1,y_2);
        if(a[i]%g){cout<<"-1\n";return ;}
        mod[i]/=g;
        a[i]=mul((y_1%mod[i]+mod[i])%mod[i],a[i]/g,mod[i]);
    }
    cout<<ex_CRT()<<"\n";
}

inline void init(){s.clear();}

signed main(){
    for(int re T=getint();T--;)init(),solve();
    return 0;
}

[51nod1135]原根

传送门
解析:

找原根裸题,由于题目保证给出的质数,所以可以直接对 p 1 p-1 分解质因数后处理。

代码:

inline int quickpow(int a,int b,int mod){
	re int res=1;
	while(b){
		if(b&1)res=(ll)a*res%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}

cs int P=100005;
bool mark[P];
int prime[P],pcnt;
inline void linear_sieves(int len=P-5){
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
		}
	}
}

int factor[10],cnt;
inline void sieve(int a){
	for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
		if(a%i==0){
			factor[++cnt]=i;
			while(a%i==0)a/=i;
		} 
	}
	if(a^1)factor[++cnt]=a;
}

int p;
inline bool judge(int g){
	for(int re i=1;i<=cnt;++i)if(quickpow(g,(p-1)/factor[i],p)==1)return false;
	return true;
}

signed main(){
	cin>>p;
	linear_sieves();
	sieve(p-1);
	for(int re i=1;;++i)if(judge(i))return cout<<i,0;
	return 0;
}

[HDU4992]Primitive Roots

传送门

解析:

如果就是简单的暴力就能找到所有原根的话,也没必要讲这道题了。。。

考虑一个优化,找到一个原根 g g 之后,所有的 g i ( g c d ( i , ϕ ( n ) ) = 1 ) g^i(gcd(i,\phi(n))=1) 都是原根。

这个结论很显然,由欧拉定理和阶的定义易证。

于是就可以在 O ( n log n ) O(n\log n) 时间内求出所有原根了。

顺带一提,原根个数为 ϕ ( ϕ ( n ) ) \phi(\phi(n)) 也是通过这个结论证明的。

代码:
首先魔改getint,让我们能够判断文件是否已经读取结束。

	inline int getint(){
		re char c;
		while(!isdigit(c=gc()))if(c==EOF)return EOF;re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
inline int quickpow(int a,int b,int mod){
	int res=1;
	while(b){
		if(b&1)res=(ll)a*res%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}

cs int P=1000006;
int prime[78500],pcnt,phi[P],minpri[P];
bool mark[P];
inline void linear_sieves(int len=P-6){
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i,phi[i]=i-1,minpri[i]=i;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			minpri[i*prime[j]]=prime[j];
			if(i%prime[j]==0){
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}

inline bool haveg(int a){
	if(!(a&3))return false;
	if(a%2==0)a/=2;
	int p=minpri[a];
	while(a%p==0)a/=p;
	return a==1;
}

int factor[11],fcnt;
inline void sieve(int a){
	fcnt=0;
	for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
		if(a%i==0){
			factor[++fcnt]=i;
			while(a%i==0)a/=i;
		} 
	}
	if(a^1)factor[++fcnt]=a;
}

inline bool judge(int g,int a){
	if(__gcd(g,a)!=1)return false;
	for(int re i=1;i<=fcnt;++i)if(quickpow(g,(phi[a])/factor[i],a)==1)return false;
	return true;
}

int g[P],gcnt;
inline void solve(int a){
	if(a==2)return (void)(cout<<"1\n");
	if(a==4)return (void)(cout<<"3\n");
	if(!haveg(a))return (void)(cout<<"-1\n");
	sieve(phi[a]);
	for(int re i=1;;++i)if(judge(i,a)){g[gcnt=1]=i;break;}
	for(int re i=2;i<phi[a];++i){
		if(__gcd(i,phi[a])==1)g[++gcnt]=quickpow(g[1],i,a);
	}
	sort(g+1,g+gcnt+1);
	for(int re i=1;i<=gcnt;++i){
		cout<<g[i]<<(i==gcnt?'\n':' ');
	}
}

int n;
signed main(){
	linear_sieves();
	while((n=getint())!=EOF)solve(n);
	return 0;
}

[TJOI2007]可爱的质数

传送门
解析:

BSGS板题。

代码:

inline int quickpow(int a,int b,int mod){
    int res=1;
    while(b){
        if(b&1)res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        b>>=1;
    }
    return res;
}

unordered_map<int,int> ma;

inline void BSGS(int A,int B,int C){
    int step=ceil(sqrt(C));
    for(int re i=0,x=B;i<=step;++i){
        ma[x]=i;
        x=(ll)x*A%C;
    }
    int g=quickpow(A,step,C);
    for(int re i=1,x=g;i<=step;++i){
        if(ma.find(x)!=ma.end())return (void)(cout<<i*step-ma[x]);
        x=(ll)x*g%C;
    }
    cout<<"no solution";
}

int A,B,C;
signed main(){
    cin>>C>>A>>B;
    BSGS(A,B,C);
    return 0;
}

[CQOI2018]破解D-H协议

传送门
解析:
显然,有了原根就可以直接BSGS了。

值得注意的是,这道题只需要对一个数计算BSGS就行了,直接对另一个数进行快速幂即可。

这就是为什么WOJ上这道题目前AC速度这么玄学。。。

代码:

int g,mod,step;
inline int quickpow(int a,int b,int res=1){
    for(;b;b>>=1,a=(ll)a*a%mod)if(b&1)res=(ll)res*a%mod;
    return res;
}

tr1::unordered_map<int,int> ma;

inline void init(){
    step=ceil(sqrt(mod));
    int now=quickpow(g,step),base=now;
    for(int re i=2;i<=step;++i){
        now=(ll)base*now%mod;
        ma[now]=i*step;
    }
}

inline int BSGS(int a){
    int now=1;
    for(int re j=0;j<=step;++j){
        if(ma.find((ll)now*a%mod)!=ma.end())return ma[(ll)now*a%mod]-j;
        now=(ll)now*g%mod;
    }
}

int n;
signed main(){
    g=getint();
    mod=getint();
    init();
    n=getint();
    while(n--){
        int A=getint(),B=getint();
        cout<<quickpow(A,BSGS(B))<<'\n';
    }
    return 0;
} 

[POJ3243]Clever Y

传送门
解析:

扩展BSGS板子题,由于丧心病狂的POJ不让用unordered_map,所以需要手写哈希表(手写的跑得比香港记者还快 )。

代码:

inline int quickpow(int a,int b,int mod){
	re int res=1;
	while(b){
		if(b&1)res=(ll)res*a%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}

inline void ex_gcd(int a,int b,int &x,int &y){
	if(!b){
		x=1,y=0;
		return ;
	}
	ex_gcd(b,a%b,y,x);
	y-=a/b*x;
}

inline int inv(int a,int p){
	int x,y;
	ex_gcd(a,p,x,y);
	return (x%p+p)%p;
}

cs int magic=189859;
struct Map{
	int val[magic],key[magic];
	int sta[magic],top;
	int dust;
	Map(){memset(key,-1,sizeof key);}
	void clear(){
		while(top){
			key[sta[top]]=-1;
			val[sta[top]]=0;
			top--;
		}
	}
	cs int &operator[](cs int &k)cs{
		int h=k%magic;
		while(key[h]!=-1&&key[h]!=k)h=(h+1)%magic;
		return val[h];
	}
	int &operator[](cs int &k){
		int h=k%magic;
		while(key[h]!=-1&&key[h]!=k)h=(h+1)%magic;
		if(key[h]==-1){
			sta[++top]=h;
			key[h]=k;
		}
		return val[h];
	}
	bool find(cs int &k)cs{
		int h=k%magic;
		while(key[h]!=-1&&key[h]!=k)h=(h+1)%magic;
		return key[h]==k;
	}
}ma;

inline int BSGS(int A,int B,int C){
	A%=C;B%=C;
	if(B==1)return 0;
	int step=ceil(sqrt(C));
	ma.clear();
	 for(int re i=0,x=B;i<=step;++i){
        ma[x]=i;
        x=(ll)x*A%C;
    }
    int g=quickpow(A,step,C);
    for(int re i=1,x=g;i<=step;++i){
        if(ma.find(x))return i*step-ma[x];
        x=(ll)x*g%C;
    }
	return -1;
}

inline int gcd(int a,int b){
	if(!a||!b)return a|b;
	re int shift=__builtin_ctz(a|b);
	for(b>>=__builtin_ctz(b);a;a-=b)if((a>>=__builtin_ctz(a))<b)swap(a,b);
	return b<<shift;
}

inline int ex_BSGS(int A,int B,int C){
	A%=C;B%=C;
	if(B==1)return 0;
	re int D=1,cnt=0;
	for(int re g=gcd(A,C);g^1;g=gcd(A,C)){
		if(B%g)return -1;
		B/=g;C/=g;
		D=(ll)D*A/g%C;
		++cnt;
		if(B==D)return cnt;
	}
	re int ans=BSGS(A,(ll)B*inv(D,C)%C,C);
	if(ans==-1)return -1;
	return ans+cnt;
}

int A,B,C;
signed main(){
	while(scanf("%d%d%d",&A,&C,&B),A|B|C){
		int ans=ex_BSGS(A,B,C);
		(~ans)?cout<<ans<<"\n":cout<<"No Solution\n";
	}
	return 0;
}

[51nod1038]X^A Mod P

传送门

模质数的n次剩余板子题。

求所有解的话就在扩欧那里带上所有解就行了。

代码:

cs int P=100005;
int prime[P],pcnt;
bool mark[P];
inline void linear_sieves(int len=P-5){
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
		}
	}
}

inline int quickpow(int a,int b,int mod){
	re int res=1;
	while(b){
		if(b&1)res=(ll)res*a%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}

int factor[30],fcnt;
inline void sieve(int a){
	fcnt=0;
	for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
		if(a%i==0){
			factor[++fcnt]=i;
			while(a%i==0)a/=i;
		} 
	}
	if(a^1)factor[++fcnt]=a;
}

inline bool judge(int g,int a){
	for(int re i=1;i<=fcnt;++i)if(quickpow(g,(a-1)/factor[i],a)==1)return false;
	return true;
}


inline int find_g(int p){
	sieve(p-1);
	for(int re i=1;i<p;++i)if(judge(i,p))return i;
	assert(0);
}

cs int magic=189859;
struct Map{
	int val[magic],key[magic];
	int sta[magic],top;
	Map(){memset(key,-1,sizeof key);}
	void clear(){
		while(top){
			key[sta[top]]=-1;
			val[sta[top]]=0;
			top--;
		}
	}
	cs int &operator[](cs int &k)cs{
		int h=k%magic;
		while(key[h]!=-1&&key[h]!=k)h=(h+1)%magic;
		return val[h];
	}
	int &operator[](cs int &k){
		int h=k%magic;
		while(key[h]!=-1&&key[h]!=k)h=(h+1)%magic;
		if(key[h]==-1){
			sta[++top]=h;
			key[h]=k;
		}
		return val[h];
	}
	bool find(cs int &k)cs{
		int h=k%magic;
		while(key[h]!=-1&&key[h]!=k)h=(h+1)%magic;
		return key[h]==k;
	}
}ma;

inline int BSGS(int A,int B,int C){
	A%=C;B%=C;
	if(B==1)return 0;
	int step=ceil(sqrt(C));
	ma.clear();
	 for(int re i=0,x=B;i<=step;++i){
        ma[x]=i;
        x=(ll)x*A%C;
    }
    int g=quickpow(A,step,C);
    for(int re i=1,x=g;i<=step;++i){
        if(ma.find(x))return i*step-ma[x];
        x=(ll)x*g%C;
    }
	assert(0);
}

inline int ex_gcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1;y=0;
		return a;
	}
	int t=ex_gcd(b,a%b,y,x);
	y-=a/b*x;
	return t;
}

vector<int> ans;
inline void get_ans(int n,int t,int p){//yn=t mod p -> y_1n+y_2p=t
	ans.clear();
	re int y_1,y_2;
	re int g=ex_gcd(n,p,y_1,y_2);
	if(t%g)return ;
	n/=g,t/=g;
	int P=p/g;
	y_1=((ll)y_1*t%P+P)%P;
	while(y_1<p)ans.push_back(y_1),y_1+=P;
}

int N,C,B;
int T;
signed main(){
	linear_sieves();
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&C,&N,&B);//x^N=B mod C
		int g=find_g(C);
		int t=BSGS(g,B,C);
		get_ans(N,t,C-1);
		if(ans.size()==0)puts("No Solution");
		else {
			for(int re i=0;i<ans.size();++i)ans[i]=quickpow(g,ans[i],C);
			sort(ans.begin(),ans.end());
			for(int re i=0;i<ans.size();++i)cout<<ans[i]<<(i==ans.size()-1?'\n':' ');
		}
	}
	return 0;
}

[HDU3930]Broot

传送门
解析:

加强版,数据较大需要用快速乘,本来按理说应该要用Pollard-Rho的,不过数据水到一定程度了。。。

顺带一提,这道题rank榜第一页的所有代码(只要是博客里能找到的)几乎都能被卡,因为数据水才跑得那么快。

但是这个复杂度。。。如果让我来出数据,估计任何做法都卡不过去。

由于模数巨大所以需要用map,Hash表冲突概率略大。

这什么锤子时空复杂度

代码:

cs int P=10000005;
int prime[P],pcnt;
bool mark[P];
inline void linear_sieves(int len=P-5){
    for(int re i=2;i<=len;++i){
        if(!mark[i])prime[++pcnt]=i;
        for(int re j=1;i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j]==0)break;
        }
    }
}

inline ll mul(ll a,ll b,ll mod){
    return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}

inline ll quickpow(ll a,ll b,ll mod){
    re ll res=1;
    while(b){
        if(b&1)res=mul(res,a,mod);
        a=mul(a,a,mod);
        b>>=1;
    }
    return res;
}

ll factor[100],fcnt;
inline void sieve(ll a){
    fcnt=0;
    for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
        if(a%i==0){
            factor[++fcnt]=i;
            while(a%i==0)a/=i;
        } 
    }
    if(a>1)factor[++fcnt]=a;
}

inline bool judge(int g,ll a){
    for(int re i=1;i<=fcnt;++i)if(quickpow(g,(a-1)/factor[i],a)==1)return false;
    return true;
}


inline int find_g(ll p){
    sieve(p-1);
    for(int re i=1;i<p;++i)if(judge(i,p))return i;
}

inline ll BSGS(ll A,ll B,ll C){
    A%=C;
    B%=C;
    ll step=ceil(sqrt(C));
    map<ll,ll> ma;
    for(ll re i=0,x=B;i<=step;++i){
        ma[x]=i;
        x=mul(x,A,C);
    }
    ll g=quickpow(A,step,C);
    for(ll re i=1,x=g;i<=step;++i){
        if(ma.count(x))return i*step-ma[x];
        x=mul(x,g,C);
    }
}

inline ll ex_gcd(ll a,ll b,ll &x,ll &y){
    if(b==0){
        x=1;y=0;
        return a;
    }
    ll t=ex_gcd(b,a%b,y,x);
    y-=a/b*x;
    return t;
}

vector<ll> ans;
inline void get_ans(ll n,ll t,ll p){
    ans.clear();
    re ll y_1,y_2;
    re ll g=ex_gcd(n,p,y_1,y_2);
    if(t%g)return ;
    n/=g,t/=g;
    ll P=p/g;
    y_1=(y_1%P+P)%P;
    y_1=(mul(y_1,t,P)+P)%P;
    while(y_1<p)ans.push_back(y_1),y_1+=P;
}

ll N,C,B;
int t=1;
signed main(){
    linear_sieves();
    while(~scanf("%I64d%I64d%I64d",&N,&C,&B)){
        cout<<"case"<<t++<<":"<<"\n";
        ll g=find_g(C);
        ll t=BSGS(g,B,C);
        get_ans(N,t,C-1);
        if(ans.size()==0)puts("-1");
        else {
            for(int re i=0;i<ans.size();++i)ans[i]=quickpow(g,ans[i],C);
            sort(ans.begin(),ans.end());
            for(int re i=0;i<ans.size();++i)cout<<ans[i]<<"\n";
        }
    }
    return 0;
}

[BZOJ2219]数论之神

传送门
解析

显然可以通过CRT将这个模数分解 P = i = 1 t p i k i P=\prod_{i=1}^tp_i^{k_i}

解出来之后可以用CRT合并,而各个方程本身是互不相关的。

所以最终解的个数由乘法原理可以知道是各个方程的解的个数相乘。

现在考虑求解这个方程: x A B ( m o d p α ) x^A\equiv B\pmod {p^\alpha}

分三种情况讨论一下:

1. p α B p^\alpha\mid B
此时的方程等价于: x A 0 ( m o d p α ) x^A\equiv 0\pmod{p^\alpha}

x = p t s x=p^ts ,显然有 t A α tA\geq\alpha ,所以最小的 t = α A t=\lceil\frac{\alpha}A\rceil

此时方程解的个数为 p α α A p^{\alpha-\lceil\frac{\alpha}{A}\rceil}

2. p B p\nmid B

这时候就是裸的同余方程: x A B ( m o d p α ) x^A\equiv B\pmod {p^\alpha}

利用指标转化: A i n d g x i n d g B ( m o d p α ) A\cdot ind_gx\equiv ind_gB\pmod{p^\alpha}

直接用 e x g c d exgcd 解这个方程得到解的数量即可。

3. p B p\mid B

B = p c n t b B=p^{cnt}b ,得到 x A p c n t b ( m o d p α ) x^A\equiv p^{cnt}b\pmod {p^{\alpha}}

显然必须要有 A c n t A\mid cnt 上面这个方程才能有解。

x 0 = x p c n t A x_0=\frac{x}{p^{\frac{cnt}A}} 转化为 x 0 A b ( m o d p α c n t ) x_0^A\equiv b\pmod{p^{\alpha-cnt}}

于是转化成了情况2。

但是!!!!

注意一个问题,我们真的这样就得到全部解了吗?

注意到原方程 x x 的取值范围为 [ 0 , p α ) [0,p^{\alpha}) x 0 x_0 的取值范围为 [ 0 , p α c n t A ) [0,p^{\alpha-\frac{cnt}A}) ,结果最后一个方程的 x 0 x_0 的取值范围为 [ 0 , p α c n t ) [0,p^{\alpha-cnt})

也就是说,在这个模产生的周期中,我们只计算了 p c n t c n t A p^{cnt-\frac{cnt}A} 个周期中的一个。乘上就行了。

代码:

inline int quickpow(int a,int b,int mod=2147483647){
	re int res=1;
	while(b){
		if(b&1)res=(ll)res*a%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}

inline int gcd(int a,int b){
	if(!a||!b)return a|b;
	re int shift=__builtin_ctz(a|b);
	for(b>>=__builtin_ctz(b);a;a-=b)if((a>>=__builtin_ctz(a))<b)swap(a,b);
	return b<<shift;
}

inline int ex_gcd(int a,int b,int &x,int &y){
	if(!b){
		x=1,y=0;
		return a;
	}
	int t=ex_gcd(b,a%b,y,x);
	y-=(a/b)*x;
	return t;
}

cs int P=100005;
int prime[P],pcnt;
bool mark[P];
inline void linear_sieves(int len=P-5){
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
		}
	}
}

int fact[20],fcnt;
inline void sieve2(int a){
	fcnt=0;
	for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
		if(a%i==0){
			fact[++fcnt]=i;
			while(a%i==0)a/=i;
		}
	}
	if(a>1)fact[++fcnt]=a;
}

struct Factor{int p,time,P;};
vector<Factor> factor;

inline void sieve(int a){
	factor.clear();
	for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
		if(a%i==0){
			factor.push_back(Factor{i,0,1});
			while(a%i==0)a/=i,factor.back().time++,factor.back().P*=i;
		}
	}
	if(a^1)factor.push_back(Factor{a,1,a});
}

inline bool judge(int g,int p,int phi){
	for(int re i=1;i<=fcnt;++i)if(quickpow(g,phi/fact[i],p)==1)return false;
	return true;
}

inline int find_g(int p,int phi){
	sieve2(phi);
	for(int re i=2;i<p;++i)if(judge(i,p,phi))return i;
}

cs int magic=189859;
struct Map{
	int val[magic],key[magic];
	int sta[magic],top;
	Map(){memset(key,-1,sizeof key);}
	
	void clear(){
		while(top){
			key[sta[top]]=-1;
			val[sta[top]]=0;
			--top;
		}
	}
	
	cs int &operator[](cs int &k)cs{
		int h=k%magic;
		while((~key[h])&&(key[h]^k))h=(h+1)%magic;
		return val[h];
	}
	int &operator[](cs int &k){
		int h=k%magic;
		while((~key[h])&&(key[h]^k))h=(h+1)%magic;
		if(key[h]==-1){
			sta[++top]=h;
			key[h]=k;
		}
		return val[h];
	}
	bool find(cs int &k)cs{
		int h=k%magic;
		while((~key[h])&&(key[h]^k))h=(h+1)%magic;
		return key[h]==k;
	}
}ma;

inline int BSGS(int A,int B,int C){
	A%=C,B%=C;
	if(B==1)return 0;
	re int step=ceil(sqrt(C));
	ma.clear();
	for(int re i=0,x=B;i<=step;++i){
		ma[x]=i;
		x=(ll)x*A%C;
	}
	int g=quickpow(A,step,C);
	for(int re i=1,x=g;i<=step;++i){
		if(ma.find(x))return i*step-ma[x];
		x=(ll)x*g%C;
	}
	return -1;
}

int cnt;
inline int solve(int A,int B,cs Factor &data){
	int C=data.P;
	int phi=C-C/data.p;
	int g=find_g(C,phi);
	int ind=BSGS(g,B,C);
	int ans=gcd(phi,A);
	if(ind%ans)return 0;
	return ans*quickpow(data.p,cnt-cnt/A);
}

int T;
int A,B,K,p;
signed main(){
	linear_sieves();
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&A,&B,&K);
		p=K*2+1;
		sieve(p);
		int ans=1;
		for(int re i=0;i<factor.size();++i){
			if(!ans)break;
			Factor &data=factor[i];
			if(B%data.P){
				int b=B;
				cnt=0;
				while(b%data.p==0){
					b/=data.p;
					data.P/=data.p;
					data.time--;
					++cnt;
				}
				if(cnt%A)ans=0;
				else ans=ans*solve(A,b,data);
			}
			else ans=ans*quickpow(data.p,data.time-(data.time-1)/A-1);
		}
		cout<<ans<<'\n';
	}
	return 0;
}

[51nod1123]X^A Mod B

传送门
解析:
你要是以为数论没有码农题可就太naiive了。

注意这里是 m o d &ThinSpace;&ThinSpace; B \mod B 不是 m o d &ThinSpace;&ThinSpace; P \mod P ,就是说,模数任意。

所以这是个什么玩意。。。任意模数 n n 次剩余。。。

直接模拟吧。。。

代码:

inline int quickpow(int a,int b,int mod=2147483647){
	re int res=1;
	while(b){
		if(b&1)res=(ll)res*a%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}

inline int ex_gcd(int a,int b,int &x,int &y){
	if(!b){
		x=1,y=0;
		return a;
	}
	re int t=ex_gcd(b,a%b,y,x);
	y-=a/b*x;
	return t;
}

inline int gcd(int a,int b){
	if(!a||!b)return a|b;
	re int shift=__builtin_ctz(a|b);
	for(b>>=__builtin_ctz(b);a;a-=b)if((a>>=__builtin_ctz(a))<b)swap(a,b);
	return b<<shift;
}

cs int P=100005;
int prime[P],pcnt;
bool mark[P];
inline void linear_sieves(int len=P-5){
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
		}
	}
}

struct Map{
	static cs int magic=189859;
	int key[magic],val[magic];
	int sta[magic],top;
	Map(){memset(key,-1,sizeof key);}
	
	void clear(){
		while(top){
			key[sta[top]]=-1;
			val[sta[top]]=0;
			--top;
		}
	}
	
	cs int &operator[](cs int &k)cs{
		int h=k%magic;
		while((~key[h])&&(key[h]!=k))h=(h+1)%magic;
		return val[h];
	}
	
	int &operator[](cs int &k){
		int h=k%magic;
		while((~key[h])&&(key[h]!=k))h=(h+1)%magic;
		if(key[h]==-1){
			sta[++top]=h;
			key[h]=k;
		}
		return val[h];
	}
	bool find(cs int &k)cs{
		int h=k%magic;
		while((~key[h])&&key[h]!=k)h=(h+1)%magic;
		return key[h]==k;
	}
}ma;

inline int BSGS(int A,int B,int C){//A^x=B mod C
	A%=C;B%=C;
	if(B==1)return 0;
	ma.clear(); 
	int step=ceil(sqrt(C));
	for(int re i=0,x=B;i<=step;++i){
		ma[x]=i;
		x=(ll)x*A%C;
	}
	int g=quickpow(A,step,C);
	for(int re i=1,x=g;i<=step;++i){
		if(ma.find(x))return i*step-ma[x];
		x=(ll)x*g%C;
	}
	return -1;
}

int fact[60],fcnt;
inline void sieve2(int a){
	fcnt=0;
	for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
		if(a%i==0){
			fact[++fcnt]=i;
			while(a%i==0)a/=i;
		}
	}
	if(a^1)fact[++fcnt]=a;
}

struct Factor{int p,time,P;};
vector<Factor> factor;
inline void sieve(int a){
	factor.clear();
	for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
		if(a%i==0){
			factor.push_back(Factor{i,0,1});
			while(a%i==0)a/=i,factor.back().time++,factor.back().P*=i;
		}
	}
	if(a^1)factor.push_back(Factor{a,1,a});
}

inline bool judge(int g,int p,int phi){
	for(int re i=1;i<=fcnt;++i)if(quickpow(g,phi/fact[i],p)==1)return false;
	return quickpow(g,phi,p)==1;
}

inline int find_g(int p,int phi){
	sieve2(phi);
	for(int re i=1;i<p;++i)if(judge(i,p,phi))return i;
}

void dfs_2(vector<int> &vec,cs int &N,cs int &B,cs int &c,cs int &t,cs int &d){
	if(quickpow(t,N,1<<d)!=B%(1<<d))return ;
	if(c==d)return vec.push_back(t);
	dfs_2(vec,N,B,c,t,d+1);
	dfs_2(vec,N,B,c,t|(1<<d),d+1);
}
inline bool solve2(vector<int> &vec,int N,int B,int c){
	vec.clear();
	B%=1<<c;
	dfs_2(vec,N,B,c,0,0);
	return vec.size();
}

inline void get_ans(vector<int> &vec,int N,int B,int p,int c){
	vec.clear();
	int C=quickpow(p,c),phi=C-C/p;
	int g=find_g(C,phi);
	int t=BSGS(g,B,C);
	int Gcd=gcd(phi,gcd(N,t));
	if(gcd(N,phi)>Gcd)return ;
	int _N=N/Gcd,_t=t/Gcd,np=phi/Gcd;
	int y_1,y_2;
	ex_gcd(_N,np,y_1,y_2);
	y_1=(y_1%np+np)%np;
	y_1=(ll)y_1*_t%np;
	for(int re i=y_1;i<phi;i+=np)vec.push_back(quickpow(g,i,C));
}

inline bool solve(vector<int> &vec,int N,int B,cs Factor &data){
	if(data.p==2)return solve2(vec,N,B,data.time);
	vec.clear();
	int C=data.P,phi=C-C/data.p,p=data.p;
	B%=C;
	if(B==0){
		int base=quickpow(p,(data.time+N-1)/N);
		for(int re i=0;i<C;i+=base)
		vec.push_back(i);
		return vec.size();
	}
	int G=gcd(B,C),cnt=0;
	B/=G;
	while(G>1)G/=p,++cnt;
	if(cnt%N)return false;
	static vector<int> tmp;
	get_ans(tmp,N,B,p,data.time-cnt);
	int base=quickpow(p,data.time-cnt+cnt/N),t=quickpow(p,cnt/N);
	for(int re i=0;i<tmp.size();++i)
	for(int re j=(ll)tmp[i]*t%base;j<C;j+=base)vec.push_back(j);
	return vec.size()>0;
}

vector<int> res[50];
int e[50];
set<int> ans;

void dfs_CRT(int x,int d,int C){
	if(d==factor.size())return (void)ans.insert(x);
	for(int re i=0;i<res[d].size();++i)
	dfs_CRT(((ll)res[d][i]*e[d]+x)%C,d+1,C);
}

inline void solve(int N,int B,int C){
	sieve(C);
	for(int re i=0;i<factor.size();++i)
	if(!solve(res[i],N,B,factor[i]))return (void)puts("No Solution");
	for(int re i=0;i<factor.size();++i){
		int v1=factor[i].P,v2=C/factor[i].P;
		int y_1,y_2;
		ex_gcd(v2,v1,y_1,y_2);
		y_1=(y_1%v1+v1)%v1;
		e[i]=(ll)v2*y_1%C;
	}
	ans.clear();
	dfs_CRT(0,0,C);
	for(set<int>::iterator it=ans.begin();it!=ans.end();++it)cout<<*it<<" ";
	cout<<"\n";
}

int T;
int A,B,C;
signed main(){
	linear_sieves();
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&A,&C,&B);
		solve(A,B,C);
	}
	return 0;
}

[Timus1132]Square Root

传送门

解析:

膜奇质数的二次剩余直接上Cipolla就行了。

可以当做是二次剩余的一个板子题。

代码:

inline ll mul(cs ll &a,cs ll &b,cs ll &mod){
	return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}

struct complex{
	ll x,y;
	complex():x(1),y(0){}
	complex(ll _x,ll _y):x(_x),y(_y){}
};
ll W,mod;
complex operator*(cs complex &a,cs complex &b){
	return complex((mul(a.x,b.x,mod)+mul(mul(a.y,b.y,mod),W,mod))%mod,(mul(a.x,b.y,mod)+mul(a.y,b.x,mod))%mod);
}

inline ll quickpow(ll a,ll b){
	ll ans=1;
	for(;b;b>>=1,a=mul(a,a,mod))if(b&1)ans=mul(ans,a,mod);
	return ans;
}


inline complex quickpow(complex a,ll b){
	complex res;
	for(;b;b>>=1,a=a*a)if(b&1)res=res*a;
	return res;
}

inline ll solve(ll a){
	if(mod==2)return 1;
	if(quickpow(a,(mod-1)>>1)==mod-1)return -1;
	re ll b;
	while(true){
		b=rand()%mod;
		W=(mul(b,b,mod)-a+mod)%mod;
		if(quickpow(W,(mod-1)>>1)==mod-1)break;
	}
	return quickpow(complex(b,1),(mod+1)>>1).x;
}

int T;ll a;
signed main(){
	T=getint();
	srand(time(0));
	while(T--){
		a=getint();mod=getint();
		a%=mod;
		a=solve(a);
		if(~a){
			re ll b=mod-a;
			if(a>b)swap(a,b);
			if(a^b)outint(a),pc(' '),outint(b),pc('\n');
			else outint(a),pc('\n');
		}
		else puts("No root");
	}
	return 0;
}

[SCOI2018]Numazu 的蜜柑

题目大意:
给定一棵有 n n 个节点的树,每个节点有点权 a i a_i 给出 p , A , B p,A,B ,问有多少点对 ( u , v ) (u,v) 满足:

  1. v v u u 的祖先。
  2. a u 2 + A a u a v + B a v 2 0 ( m o d p ) a^2_u+Aa_ua_v+Ba^2_v\equiv 0\pmod p
    n 100000 , p P , 3 p 1 0 16 , 0 A , B &lt; p n≤100000,p∈P,3≤p≤10^{16},0≤A,B&lt;p

解析:
直接解方程可以得到 a u a v A ± A 2 4 B 2 ( m o d p ) a_u\equiv a_v\frac{-A\pm\sqrt{A^2-4B}}{2}\pmod p

利用二次剩余直接解出 d e t A 2 4 B ( m o d p ) det\equiv \sqrt{A^2-4B}\pmod p

然后分情况讨论。

如果有解,一边dfs树的时候一边用map 统计一下就好了。

如果无解,dfs只需要记录它上方有多少个祖先的权值为0,然后看它自己的权值是否为0,那么它和祖先就能组成合法点对,记录就行了。

代码:

inline ll mul(cs ll &a,cs ll &b,cs ll &mod){return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;}

namespace Find_root{
	inline ll quickpow(ll a,ll b,cs ll &mod,ll res=1){
		for(;b;b>>=1,a=mul(a,a,mod))if(b&1)res=mul(res,a,mod);
		return res;
	}
	ll W,Mod;
	struct Complex{
		ll x,y;
		Complex(cs ll &_x=0,cs ll &_y=0):x(_x),y(_y){}
		inline friend Complex operator*(cs Complex &a,cs Complex &b){
			return Complex(
				(mul(a.x,b.x,Mod)+mul(mul(a.y,b.y,Mod),W,Mod))%Mod,
				(mul(a.x,b.y,Mod)+mul(a.y,b.x,Mod))%Mod
			);
		}
	};
	
	inline Complex quickpow(Complex a,ll b){
		re Complex res(1,0);
		for(;b;b>>=1,a=a*a)if(b&1)res=res*a;
		return res;
	}
	
	inline ll solve(ll a,ll p){
		a%=p;if(a==0)return 0;
		if(quickpow(a,(p-1)>>1,p)==p-1)return -1;
		re ll b;
		Mod=p;
		while(true){
			b=rand()%p;
			W=(mul(b,b,p)-a+p)%p;
			if(quickpow(W,(p-1)>>1,p)==p-1)break;
		}
		return quickpow(Complex(b,1),(p+1)>>1).x;
	}
}

int n;
ll p,A,B;

cs int N=100005;
vector<int> edge[N];
inline void addedge(cs int &u,cs int &v){
	edge[u].push_back(v);
}

ll a[N];
ll a1,a2,ans;
tr1::unordered_map<ll,int> cnt;

inline void dfs1(int u){
	ans+=cnt[a[u]];
	re ll v1=mul(a[u],a1,p),v2=mul(a[u],a2,p);
	v1==v2?++cnt[v1]:(++cnt[v1],++cnt[v2]);
	for(int re e=0;e<edge[u].size();++e)
	dfs1(edge[u][e]);
	v1==v2?--cnt[v1]:(--cnt[v1],--cnt[v2]);
}

int now;
inline void dfs2(int u){
	if(!a[u])ans+=now,++now;
	for(int re e=0;e<edge[u].size();++e)
	dfs2(edge[u][e]);
	if(!a[u])--now;
}

signed main(){
	n=getint();p=getint();A=getint();B=getint();
	for(int re i=1;i<=n;++i)a[i]=getint();
	for(int re i=2;i<=n;++i)addedge(getint(),i);
	ll det=Find_root::solve((mul(A,A,p)-4*B%p+p)%p,p);
	if(det==-1)dfs2(1);
	else{
		ll inv2=p-(p/2);
		a1=mul((det-A+p)%p,inv2,p);
		a2=mul((-det-A+p+p)%p,inv2,p);
		dfs1(1);
	}
	printf("%lld",ans);
	return 0;
}

[WOJ4270]任意模数二次剩余

题目大意:
求解关于 x x 的方程:
x 2 a ( m o d p ) x^2≡a\pmod p
保证所有 p 1 e 15 T 5000 p≤1e15,T≤5000 ,不保证 a &lt; p a&lt;p
多解只需要给出任意一个

解析:

任意模数二次剩余裸题,由于需要卡 O ( n ) O(\sqrt n) n n 次剩余做法,数据范围有点大,需要用Pollard-Rho分解模数。

代码:

namespace Linear_sieves{
	cs int P=300005;
	int prime[P],pcnt;
	bool mark[P];
	
	inline void init(int len=P-5){
		mark[1]=true;
		for(int re i=2;i<=len;++i){
			if(!mark[i])prime[++pcnt]=i;
			for(int re j=1;j<=pcnt&&i*prime[j]<=len;++j){
				mark[i*prime[j]]=true;
				if(i%prime[j]==0)break;
			}
		}
	}
}

namespace Find_root{
	#define complex COMPLEX
	using namespace Linear_sieves;
	
	inline ll mul(cs ll &a,cs ll &b,cs ll &mod){
		return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
	}
	inline ll quickpow(ll a,ll b,cs ll &mod,ll res=1){
		for(;b;b>>=1,a=mul(a,a,mod))if(b&1)res=mul(res,a,mod);
		return res;
	}
	
	inline ll ex_gcd(cs ll &a,cs ll &b,ll &x,ll &y){
		if(!b){
			y=0;
			x=1;
			return a;
		}
		ll t=ex_gcd(b,a-a/b*b,y,x);
		y-=(a/b)*x;
		return t;
	}
	inline ll inv(cs ll a,cs ll mod){
		ll x,y;
		ll t=ex_gcd(a,mod,x,y);
		return (x%mod+mod)%mod;
	}
	
	ll W,Mod;
	class complex{
		public:
			ll x,y;
			complex(cs ll &_x=0,cs ll &_y=0):x(_x),y(_y){}
			inline friend complex operator*(cs complex &a,cs complex &b){
				return complex(
					(mul(a.x,b.x,Mod)+mul(mul(a.y,b.y,Mod),W,Mod))%Mod,
					(mul(a.x,b.y,Mod)+mul(a.y,b.x,Mod))%Mod);
			}
	};
	
	complex quickpow(complex a,ll b){
		complex res(1,0);
		for(;b;b>>=1,a=a*a)if(b&1)res=res*a;
		return res;
	}
	
	inline bool isprime(ll x){
		if(x<=P-5)return !mark[x];
		if(x%2==0||x%3==0||x%5==0||x%7==0||x%31==0||x%24251==0)return false;
		re ll t=x-1,s;
		t>>=(s=__builtin_ctzll(t));
		for(int re i=1;i<=5;++i){
			re ll p=prime[rand()%pcnt+1];
			re ll num=quickpow(p,t,x),pre=num;
			for(int re j=0;j<s;++j){
				num=mul(num,num,x);
				if(num==1&&pre!=x-1&&pre!=1)return false;
				pre=num;
				if(num==1)break;
			}
			if(num^1)return false;
		}
		return true;
	}
	
	inline ll Pollard_rho(ll x){
		if(x%2==0)return 2;
		if(x%3==0)return 3;
		if(x%5==0)return 5;
		if(x%7==0)return 7;
		if(x%31==0)return 31;
		if(x%24251==0)return 24251;
		re ll n=0,m=0,t=1,q=1,c=rand()%(x-2)+2;
		for(int re k=2;;k<<=1,m=n,q=1){
			for(int re i=1;i<=k;++i){
				n=(mul(n,n,x)+c)%x;
				q=mul(q,abs(n-m),x);
			}
			if((t=__gcd(q,x))>1)return t;
		}
	}
	
	ll fact[60],cntf;
	inline void sieves(ll x){
		if(x==1)return ;
		if(isprime(x)){fact[++cntf]=x;return;}
		re ll p=x;
		while(p==x)p=Pollard_rho(p);
		sieves(p);
		while(x%p==0)x/=p;
		sieves(x);
	}
	
	inline ll solve_2k(ll a,ll k){
		if(a%(1<<k)==0)return 0;
		a%=(1<<k);
		re ll t=0,res=1;
		a>>=(t=__builtin_ctzll(a));
		if((a&7)^1)return -1;
		if(t&1)return -1;
		k-=t;
		for(int re i=4;i<=k;++i){
			res=(res+(a%(1<<i)-res*res)/2)%(1<<k);
		}
		res%=1<<k;
		if(res<0)res+=1<<k;
		return res<<(t>>1); 
	}
	
	inline ll solve_p(ll a,ll p){
		a%=p;
		if(quickpow(a,(p-1)>>1,p)==p-1)return -1;
		re ll b;
		Mod=p;
		while(true){
			b=rand()%p;
			W=(mul(b,b,p)-a+p)%p;
			if(quickpow(W,(p-1)>>1,p)==p-1)break;
		}
		re ll ans=quickpow(complex(b,1),(p+1)>>1).x;
		return min(ans,p-ans);
	}
	
	inline ll solve_pk(ll a,ll k,ll p,ll mod){
		if(a%mod==0)return 0;
		re ll t=0,hmod=1;
		while(a%p==0)a/=p,++t,hmod*=(t&1)?p:1;
		if(t&1)return -1;
		k-=t;
		mod/=hmod*hmod;
		re ll res=solve_p(a,p);
		if(res==-1)return -1;
		complex tmp(res,1);
		W=a;
		Mod=mod;
		tmp=quickpow(tmp,k);
		res=mul(tmp.x,inv(tmp.y,Mod),Mod);
		return res*hmod;
	}
	
	ll remain[20],mod[20],p;
	inline ll CRT(){
		re ll ans=0;
		for(int re i=1;i<=cntf;++i){
			ans=(ans+mul(mul(p/mod[i],inv(p/mod[i],mod[i]),p),remain[i],p))%p;
		}
		return ans;
	}
	
	inline ll solve(ll a,ll pmod){
		a%=pmod;
		cntf=0;
		p=pmod;
		sieves(pmod);
		if(cntf>1)sort(fact+1,fact+cntf+1);
		if(cntf>1)cntf=unique(fact+1,fact+cntf+1)-fact-1;
		for(int re i=1;i<=cntf;++i){
			re ll now=0,rmod=1;
			while(pmod%fact[i]==0)pmod/=fact[i],++now,rmod*=fact[i];
			mod[i]=rmod;
			if(fact[i]==2)remain[i]=solve_2k(a,now);
			else remain[i]=solve_pk(a,now,fact[i],rmod);
			if(remain[i]==-1)return -1;
		}
		return CRT();
	}
	
	#undef complex
}

int T;
signed main(){
	srand(time(0));
	Linear_sieves::init();
	T=getint();
	while(T--){
		re ll a=getint(),p=getint(),ans;
		outint(ans=Find_root::solve(a,p));pc('\n');
	}
	return 0;
} 

[洛谷P3935]Calculating

传送门

解析:

显然题目中: f = d f=d

考虑差分,设 S ( n ) = i = 1 n d ( i ) S(n)=\sum_{i=1}^{n}d(i) ,那么询问的就是 S ( r ) S ( l 1 ) S(r)-S(l-1)

考虑如何计算 S S

我们有如下结论:

S ( n ) = i = 1 n n i S(n)=\sum_{i=1}^{n}\lfloor\frac{n}i\rfloor

怎么理解?我们要统计每个数的约数个数,等价于统计每个数是全域内多少个数的约数。

比如 n 2 \lfloor\frac{n}2\rfloor 表示在 [ 1 , n ] [1,n] 中,有 n 2 \lfloor\frac{n}{2}\rfloor 个数有因子2,它们是: 2 × 1 , 2 × 2 , 2 × 3...2 × n 2 2\times 1,2\times 2,2\times 3...2\times \lfloor\frac{n}2\rfloor

所以我们现在就有这么一个式子来求值。

其实 σ k \sigma_k 的这个性质还挺好用的。

ll mod=998244353;
inline ll solve(ll n){
	ll ans=0;
	for(ll re i=1,j;i<=n;i=j+1){
		j=n/(n/i);
		(ans+=(n/i)*(j-i+1)%mod)%=mod;
	}
	return ans%mod;
}

ll l,r;
signed main(){
	cin>>l>>r;
	cout<<(solve(r)-solve(l-1)+mod)%mod;
	return 0;
} 

[LOJ125]除数函数求和 2

传送门

解析

还是像上一道题一样,直接利用除数函数的性质化简。

得到:

A n s = i = 1 n n i ( 2 i 2 + 3 i + 5 ) Ans=\sum_{i=1}^n\lfloor\frac{n}{i}\rfloor(2i^2+3i+5)

代码:

cs ll mod=998244353;
inline ll calc(ll x){return x*(x+1)%mod*(2*x+1)%mod*166374059%mod;}
ll n,a1,a2,a3;
signed main(){
	cin>>n;
	for(ll re i=1,j,t;i<=n;i=j+1){
		j=n/(n/i);
		t=n/i;
		a1=(a1+(calc(j)-calc(i-1)+mod)%mod*t%mod)%mod;
        a2=(a2+(i+j)*(j-i+1)%mod*499122177%mod*t%mod)%mod;
        a3=(a3+(j-i+1)*t)%mod;
	}
	cout<<(2*a1+3*a2+5*a3)%mod;
	return 0;
}

[CQOI2007]余数求和

传送门

我们要求的是:

i = 1 n k % i \sum_{i=1}^n k\%i

首先取模这种东西乍一看根本没法做。。。

记住一句话,取模不是一种运算,取模是一种环境

这也是为什么这个符号 \equiv 叫同余而不是其他什么东西,因为两个元素在这个环境下才是等价的。

所以要丢掉取模的符号:

A n s = i = 1 n ( k i k i ) = n k i = 1 n i k i \begin{aligned} Ans=&amp;\sum_{i=1}^n(k-i\lfloor\frac{k}{i}\rfloor)\\ =&amp;nk-\sum_{i=1}^{n}i\lfloor\frac{k}{i}\rfloor \end{aligned}

后面这个东西显然只有不超过 O ( k ) O(\sqrt k) 种取值,整除分块即可,然后 O ( 1 ) O(1) 算一算等差数列的和就行了。

代码:

int n,k;
signed main(){
	scanf("%d%d",&n,&k);
	ll ans=(ll)n*k; 
	for(int re i=1,j;i<=n;i=j+1){
		if(k/i)j=min(k/(k/i),n);
		else j=n;
		ans-=(ll)(k/i)*(j-i+1)*(j+i)/2;
	}
	printf("%lld",ans);
	return 0;
}

[洛谷P5106] dkw的lcm

传送门

解析:

由于 ϕ \phi 是积性函数,我们可以将它拆开到每个质数来考虑。

直接考虑每个 ϕ ( p t ) \phi(p^t) 在最终的答案中出现了几次。

这个显然可以容斥一下得到,将数分为三个集合:

  1. A = { x p t x x n } A=\{x|p^t\nmid x且x\leq n\}
  2. B = { x p t x p t + 1 x x n } B=\{x|p^t\mid x且p^{t+1}\nmid x且x\leq n\}
  3. C = { x p t + 1 x x n } C=\{x|p^{t+1}\mid x且x\leq n\}

显然想要最后的 l c m lcm 中出现 p t p^t ,就只能在 A , B A,B 集合中取数,并且不能 k k 个位置全部都是 A A 集合的数,方案数为: ( A + B ) k A k (|A|+|B|)^k-|A|^k ,这是 ϕ ( p t ) \phi(p^t) 在最终答案中的指数。

显然有 A + B = n n p t + 1 , B = n p t n p t + 1 |A|+|B|=n-\lfloor\frac{n}{p^{t+1}}\rfloor,|B|=\lfloor\frac{n}{p^t}\rfloor-\lfloor\frac{n}{p^{t+1}}\rfloor

计算指数的时候采用欧拉定理, % 1 e 9 + 6 \%1e9+6

代码:

inline int quickpow(int a,int b,int mod){
    re int res=1;
    while(b){
        if(b&1)res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        b>>=1;
    }
    return res;
}

cs int mod=1e9+7;
cs int P=1000006;
int prime[P],pcnt;
bool mark[P];
inline void linear_sieves(int len=P-6){
    for(int re i=2;i<=len;++i){
        if(!mark[i])prime[++pcnt]=i;
        for(int re j=1;i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j]==0)break;
        }
    }
}

int n,k;
int ans=1;
signed main(){
    cin>>n>>k;
    linear_sieves(n);
    for(int re i=1;i<=pcnt;++i){
        ll x=prime[i],phi=prime[i]-1;
        for(int re a,b,t;x<=n;x*=prime[i],phi*=prime[i]){
            a=n-n/x/prime[i];
            b=(n/x)-(n/x/prime[i]);
            t=(quickpow(a,k,mod-1)-quickpow(a-b,k,mod-1)+mod-1)%(mod-1);
            ans=(ll)ans*quickpow(phi,t,mod)%mod;
        }
    }
    cout<<(ans%mod+mod)%mod;
    return 0;
}

[BZOJ3560]DZY Loves Math V

传送门

解析:

由于 ϕ \phi 是积性函数,我们还是考虑分解后分质因子考虑,最后将不同质因子的总贡献乘起来就是我们要的答案。

所以现在我们需要解决的就是这样一个式子:

i 1 = 0 b 1 i 2 = 0 b 2 . . . i n = 0 b n ϕ ( p j = 1 n i j ) \sum_{i_1=0}^{b_1}\sum_{i_2=0}^{b_2}...\sum_{i_n=0}^{b_n}\phi(p^{\sum_{j=1}^ni_j})

由于 ϕ ( p k ) = ( p 1 ) p k 1 \phi(p^k)=(p-1)p^{k-1} ,我们可以直接处理 p k p^k 的前缀和来计算。

但是 k = 0 k=0 ,即 ϕ ( 1 ) \phi(1) 的情况不满足这个式子,很烦。只能先拿出来,做完后放回去。

所以对于每个质因子,我们这样处理(类似生成函数):

A n s = p P ( p 1 p ( ( j = 1 n i = 0 b j p i ) 1 ) + 1 ) Ans=\prod_{p\in \mathbb{P}}(\frac{p-1}p((\prod_{j=1}^n\sum_{i=0}^{b_j}p^i)-1)+1)

代码:

cs int mod=1e9+7;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}
inline int quickpow(int a,int b){
	re int res=1;
	while(b){
		if(b&1)res=(ll)res*a%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}
inline int inv(int a){return quickpow(a,mod-2);}

cs int P=10004;
int prime[P],pcnt;
bool mark[P];
inline void linear_sieves(int len=P-4){
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
		}
	}
}

struct Factor{
	int p,cnt;
	Factor(cs int &_p,cs int &_cnt=0):p(_p),cnt(_cnt){}
	friend bool operator<(cs Factor &a,cs Factor &b){
		return (a.p^b.p)?a.p<b.p:a.cnt<b.cnt;
	}
};

vector<Factor> vec;

inline void sieve(int a){
	for(int re j=1,i=prime[j];i*i<=a;i=prime[++j]){
		if(a%i==0){
			vec.push_back(Factor(i,0));
			while(a%i==0)a/=i,vec.back().cnt++;
		}
	}
	if(a^1)vec.push_back(Factor(a,1));
}

int sum[32];
int n,ans=1;
signed main(){
	n=getint();
	linear_sieves();
	while(n--)sieve(getint());
	sort(vec.begin(),vec.end());
	for(int re i=0,j;i<vec.size();i=j+1){
		for(j=i;j+1<vec.size()&&vec[j+1].p==vec[i].p;++j);
		int p=vec[i].p,up=vec[j].cnt;
		sum[0]=1;
		for(int re k=1;k<=up;++k)sum[k]=mul(sum[k-1],p);
		for(int re k=1;k<=up;++k)sum[k]=add(sum[k],sum[k-1]);
		int tmp=1;
		for(int re k=i;k<=j;++k)tmp=mul(tmp,sum[vec[k].cnt]);
		tmp=mul(dec(tmp,1),mul(p-1,inv(p)))+1;
		ans=mul(ans,tmp);
	}
	cout<<ans;
	return 0;
}

[SPOJ LCMSUM]LCM Sum

传送门

解析:

首先我们并不擅长处理 L C M LCM 问题,所以一律换成 g c d gcd

i = 1 n l c m ( i , n ) = i = 1 n i × n g c d ( i , n ) \sum_{i=1}^{n}lcm(i,n)=\sum_{i=1}^n\frac{i\times n}{gcd(i,n)}

接下来需要巧妙的转化:

A n s = i = 1 n i × n g c d ( i , n ) = 1 2 ( i = 1 n 1 i × n g c d ( i , n ) + i = 1 n 1 ( n i ) n g c d ( n i , n ) ) + n \begin{aligned} Ans=&amp;\sum_{i=1}^{n}\frac{i\times n}{gcd(i,n)}\\ =&amp;\frac{1}2(\sum_{i=1}^{n-1}\frac{i\times n}{gcd(i,n)}+\sum_{i=1}^{n-1}\frac{(n-i)*n}{gcd(n-i,n)})+n \end{aligned}

由于 g c d ( i , n ) = g c d ( n i , n ) gcd(i,n)=gcd(n-i,n) ,(不要告诉我你不会 O ( log n ) O(\log n) g c d gcd

所以我们进一步化简:

A n s = 1 2 i = 1 n 1 n 2 g c d ( i , n ) + n \begin{aligned} Ans=\frac{1}2\sum_{i=1}^{n-1}\frac{n^2}{gcd(i,n)}+n \end{aligned}

现在我们只需要考虑怎么求这个东西:

i = 1 n 1 n 2 g c d ( i , n ) \sum_{i=1}^{n-1}\frac{n^2}{gcd(i,n)}

枚举 g c d gcd
A n s = d n i = 1 n 1 n 2 d [ g c d ( i , n ) = d ] = d n n 2 d i = 1 n 1 [ g c d ( i , n ) = d ] \begin{aligned} Ans=&amp;\sum_{d\mid n}\sum_{i=1}^{n-1}\frac{n^2}{d}[gcd(i,n)=d]\\ =&amp;\sum_{d\mid n}\frac{n^2}d\sum_{i=1}^{n-1}[gcd(i,n)=d] \end{aligned}

这么明显的欧拉函数。

A n s = d n , d = ̸ n n 2 × ϕ ( n d ) d = n d n , d = ̸ 1 d × ϕ ( d ) \begin{aligned} Ans=&amp;\sum_{d\mid n,d=\not n}\frac{n^2\times \phi(\frac{n}d)}{d}\\ =&amp;n\sum_{d\mid n,d=\not1}d\times \phi(d) \end{aligned}

所以最终我们可以得到

i = 1 n l c m ( i , n ) = n 2 d n ( d ϕ ( d ) + [ d = 1 ] ) \sum_{i=1}^{n}lcm(i,n)=\frac{n}2\sum_{d\mid n}(d\phi(d)+[d=1])

我们只需要筛出欧拉函数,然后枚举倍数加到该加的地方,复杂度是调和级数。相当于 O ( n log n ) O(n\log n)

然后就可以 O ( 1 ) O(1) 回答每个询问了。

代码实现较为简单,不放了。

[HAOI2011]Problem B

传送门

解析:

基础容斥一下可以将问题转化为:

i = 1 a 1 j = 1 c 1 [ g c d ( i , j ) = k ] + i = 1 b j = 1 d [ g c d ( i , j ) = k ] i = 1 a 1 j = 1 d [ g c d ( i , j ) = k ] i = 1 b j = 1 c 1 [ g c d ( i , j ) = k ] \begin{aligned} &amp;\sum_{i=1}^{a-1}\sum_{j=1}^{c-1}[gcd(i,j)=k]+\sum_{i=1}^b\sum_{j=1}^d[gcd(i,j)=k]\\ -&amp;\sum_{i=1}^{a-1}\sum_{j=1}^d[gcd(i,j)=k]-\sum_{i=1}^b\sum_{j=1}^{c-1}[gcd(i,j)=k] \end{aligned}

发现这是四个一模一样的子问题,那么现在考虑处理:

i = 1 n j = 1 m [ g c d ( i , j ) = k ] \sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=k]

其实就是

i = 1 n k j = 1 m k [ g c d ( i , j ) = 1 ] \sum_{i=1}^{\lfloor\frac{n}k\rfloor}\sum_{j=1}^{\lfloor\frac{m}k\rfloor}[gcd(i,j)=1]

好的我们又把问题转化了一下,求

i = 1 n j = 1 m [ g c d ( i , j ) = 1 ] \sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1]

现在考虑两个函数 f f F F

f ( d ) = i = 1 n j = 1 m [ g c d ( i , j ) = d ] F ( d ) = i = 1 n j = 1 m [ d g c d ( i , j ) ] = n d m d \begin{aligned} &amp;&amp;f(d)&amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=d]\\ &amp;&amp;F(d)&amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^m[d\mid gcd(i,j)]=\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}d\rfloor \end{aligned}

显然我们就得到了一个后缀和的形式:

F ( n ) = n d f ( d ) F(n)=\sum_{n\mid d}f(d)

反演得到

f ( n ) = n d μ ( d n ) F ( d ) f(n)=\sum_{n\mid d}\mu(\frac{d}n)F(d)

所以我们要求的就是

f ( 1 ) = T = 1 min ( n , m ) μ ( T ) n T m T f(1)=\sum_{T=1}^{\min(n,m)}\mu(T)\lfloor\frac{n}T\rfloor\lfloor\frac{m}T\rfloor

整除分块就行了。

莫反的题代码就放这一道了,其他的都差不多,线筛然后分块。如果有特殊的操作我会再放代码的,需要的话自己去其他博客里面找吧。

代码:

cs int P=50004;

int prime[P],pcnt,mu[P];
bool mark[P];

inline void linear_sieves(int len=P-4){
	mu[1]=1;
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i,mu[i]=-1;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
			mu[i*prime[j]]=-mu[i];
		}
		mu[i]+=mu[i-1];
	}
}

inline ll calc(int n,int m){
	ll ans=0;
	if(n>m)swap(n,m);
	for(int re i=1,j;i<=n;i=j+1){
		j=min(n/(n/i),m/(m/i));
		ans+=(ll)(mu[j]-mu[i-1])*(n/i)*(m/i);
	}
	return ans;
}

signed main(){
	linear_sieves();
	for(int re T=getint();T--;){
		int a=getint(),b=getint(),c=getint(),d=getint(),k=getint();
		a=(a-1)/k;b/=k;c=(c-1)/k;d/=k;
		outint(calc(b,d)+calc(a,c)-calc(a,d)-calc(b,c));pc('\n');
	}
	return 0;
}

[BZOJ2820]YY的GCD

传送门

解析:

显然题目要求的是这个东西:

i = 1 n j = 1 m [ g c d ( i , j ) i s   a   p r i m e ] \sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)\mathrm{is\text{ }a\text{ }prime}]

接下来以 P \mathbb{P} 表示素数集合,下面是化简过程。

A n s = p P i = 1 n j = 1 m [ g c d ( i , j ) = p ] Ans=\sum_{p\in \mathbb{P}}\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=p]

定义函数 f f F F 如下:

f ( p ) = i = 1 n j = 1 m [ g c d ( i , j ) = p ] f(p)=\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=p]

F ( p ) = i = 1 n j = 1 m [ p g c d ( i , j ) ] F(p)=\sum_{i=1}^n\sum_{j=1}^m[p\mid gcd(i,j)]

很显然我们有

F ( p ) = p d f ( d ) F(p)=\sum_{p\mid d}f(d)

F ( p ) = n p m p F(p)=\lfloor\frac{n}{p}\rfloor\lfloor\frac{m}{p}\rfloor

接下来考虑莫比乌斯反演

f ( p ) = p d μ ( d p ) F ( d ) f(p)=\sum_{p\mid d}\mu(\frac{d}p)F(d)

A n s = p P f ( p ) = p P p d μ ( d p ) F ( d ) = p P d = 1 min ( n , m ) p μ ( d ) n d p m d p \begin{aligned} Ans=&amp;\sum_{p\in \mathbb{P}}f(p)\\ =&amp;\sum_{p\in \mathbb P}\sum_{p\mid d}\mu(\frac{d}{p})F(d)\\ =&amp;\sum_{p\in \mathbb P}\sum_{d=1}^{\lfloor\frac{ \min(n,m) }p\rfloor}\mu(d)\lfloor\frac{n}{dp}\rfloor\lfloor\frac{m}{dp}\rfloor \end{aligned}

反演后看着这个神仙式子很多人就想放弃了。

不虚啊,虚什么。
这个 d p dp 很烦是吧,那就交换枚举顺序啊

A n s = D = 1 min ( n , m ) p D , p P μ ( D p ) n D m D = D = 1 min ( n , m ) n D m D ( p D , p P μ ( D p ) ) \begin{aligned} Ans=&amp;\sum_{D=1}^{\min(n,m)}\sum_{p\mid D,p\in \mathbb P}\mu(\frac{D}{p})\lfloor\frac{n}D\rfloor\lfloor\frac{m}D\rfloor\\ =&amp;\sum_{D=1}^{\min(n,m)}\lfloor\frac{n}D\rfloor\lfloor\frac{m}D\rfloor(\sum_{p\mid D,p\in\mathbb P}\mu(\frac{D}p)) \end{aligned}

设: g ( D ) = p D , p P μ ( D p ) g(D)=\sum_{p\mid D,p\in \mathbb P}\mu(\frac{D}p)

显然我们只需要筛出所有的 μ \mu 函数就可以枚举每个质数,在 O ( n log log n ) O(n\log\log n) 时间内处理出所有 g g 函数的值,之后维护一下 g g 的前缀和,然后整除分块就行了。

但是实际上, g g 也可以在线性筛的时候一起处理出来(虽然它并不是积性函数)

首先 g ( 1 ) = 0 g(1)=0

对于一个质数 p p g ( p ) = μ ( 1 ) = 1 g(p)=\mu(1)=1

剩下的分情况考虑,如果 p n p\nmid n ,那么 p p 就是增加的一个质因子,那么原来所有有贡献的 μ \mu 都要 × 1 \times -1 ,而且会带入一个新的贡献 μ ( n ) \mu(n) ,所以有 g ( n p ) = μ ( n ) g ( n ) g(np)=\mu(n)-g(n)

如果已经有了 p n p\mid n ,那么除了 μ ( n ) \mu(n) 其他所有变量中都有 p 2 p^2 所以剩余的只有 μ ( n ) \mu(n) 了。

[NOI2010]能量采集

传送门

解析:

首先将所有格子上的数+1可以发现格子上的数就是行和列的 g c d × 2 gcd\times 2

那么问题就是这个了:

i = 1 n j = 1 m g c d ( i , j ) \sum_{i=1}^n\sum_{j=1}^mgcd(i,j)

莫比乌斯反演,首先大力推式子:

d d i = 1 n j = 1 m [ g c d ( i , j ) = d ] = d d i = 1 n d j = 1 m d [ g c d ( i , j ) = 1 ] = d d ( T = 1 min ( n , m ) d n d T m d T μ ( T ) ) \begin{aligned} &amp;\sum_{d}d\sum_{i=1}^{n}\sum_{j=1}^m[gcd(i,j)=d]\\ =&amp;\sum_{d}d\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{m}d\rfloor}[gcd(i,j)=1]\\ =&amp;\sum_{d}d(\sum_{T=1}^{\lfloor\frac{\min(n,m)}{d}\rfloor}\lfloor\frac{n}{dT}\rfloor\lfloor\frac{m}{dT}\rfloor\mu(T)) \end{aligned}

可以 O ( n n ) O(n\sqrt n) 回答每个询问了,AC此题已经没问题了。

但是我们的目标是 O ( n ) O(\sqrt n) 回答每个询问,继续化简:

考虑改变枚举顺序,这次变动有点大,请仔细观察

A n s = t = 1 min ( n , m ) n t m t D t μ ( D ) t D \begin{aligned} Ans=\sum_{t=1}^{\min(n,m)}\lfloor\frac{n}{t}\rfloor\lfloor\frac{m}t\rfloor\sum_{D\mid t}\mu(D)\frac{t}D \end{aligned}

然后后面这个东西:

D t μ ( D ) t D \sum_{D\mid t}\mu(D)\frac{t}{D}

学过 D i r i c h l e t Dirichlet 卷积的都知道, μ I d = ϕ \mu*Id=\phi ,所以这个就是个欧拉函数。

所以我们要求的东西

i = 1 n j = 1 m g c d ( i , j ) = t = 1 min ( n , m ) n t m t ϕ ( t ) \sum_{i=1}^n\sum_{j=1}^mgcd(i,j)=\sum_{t=1}^{\min(n,m)}\lfloor\frac{n}t\rfloor\lfloor\frac{m}t\rfloor\phi(t)

于是就可以整除分块了,维护一下 ϕ \phi 的前缀和就行了。

[51nod1675]序列变换

传送门

解析:

不改变其他限制,我们把原来的限制变成 1 g c d ( x , y ) 1\mid gcd(x,y) ,那么我们只需要统计所有的 d g c d ( x , y ) d\mid gcd(x,y) 对应的方案数,反演就能够得到最终的答案了。

代码:

cs int P=100005,N=P-5;
int prime[P],pcnt;
bool mark[P];
int mu[P];
inline void linear_sieves(int len=N){
	mu[1]=1;
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i,mu[i]=-1;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
			mu[i*prime[j]]=-mu[i];
		}
	}
}

int a[P],b[P],bin[P],n;
ll g[P];
signed main(){
	scanf("%d",&n);
	linear_sieves(n);
	for(int re i=1;i<=n;++i)scanf("%d",&a[i]);
	for(int re j=1;j<=n;++j)scanf("%d",&b[j]);
	for(int re d=1;d<=n;++d){
		for(int re i=d;i<=n;i+=d)++bin[a[b[i]]];
		for(int re i=d;i<=n;i+=d)g[d]+=bin[b[a[i]]];
		for(int re i=d;i<=n;i+=d)bin[a[b[i]]]=0;
	}
	ll ans=0;
	for(int re i=1;i<=n;++i)ans+=g[i]*mu[i];
	cout<<ans; 
	return 0;
}

[BZOJ2154]Crash的数字表格

传送门

解析:

我们要求的是这个式子:

i = 1 n j = 1 m l c m ( i , j ) \sum_{i=1}^n\sum_{j=1}^mlcm(i,j)

还是转化成 g c d gcd ,一波常规操作:

A n s = i = 1 n j = 1 m i × j g c d ( i , j ) = d = 1 min ( n , m ) i = 1 n j = 1 m i × j d [ g c d ( i , j ) = d ] = d = 1 min ( n , m ) d i = 1 n d j = 1 m d i j [ g c d ( i , j ) = 1 ] \begin{aligned} Ans=&amp;\sum_{i=1}^n\sum_{j=1}^m\frac{i\times j}{gcd(i,j)}\\ =&amp;\sum_{d=1}^{\min(n,m)}\sum_{i=1}^n\sum_{j=1}^m\frac{i\times j}d[gcd(i,j)=d]\\ =&amp;\sum_{d=1}^{\min(n,m)}d\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}ij[gcd(i,j)=1] \end{aligned}

好了,有这种东西就可以上 e e 的反演了,而且上面这个东西: i = 1 n d j = 1 m d i j \sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}ij 可以用分配率拆开,可以 O ( 1 ) O(1) 算出。

为了方便,定义函数 S ( n ) = i = 1 n i = n ( n + 1 ) 2 S(n)=\sum_{i=1}^ni=\frac{n(n+1)}{2}

继续推式子:

A n s = d = 1 min ( n , m ) d S ( n d ) S ( m d ) t g c d ( i , j ) , i n d , j m d μ ( t ) = d = 1 min ( n , m ) d t = 1 min ( n , m ) d t 2 μ ( t ) S ( n d t ) S ( m d t ) = T = 1 min ( n , m ) S ( n T ) S ( m T ) T d T d μ ( d ) \begin{aligned} Ans=&amp;\sum_{d=1}^{\min(n,m)}d\cdot S(\lfloor\frac{n}d\rfloor)S(\lfloor\frac{m}d\rfloor)\sum_{t \mid gcd(i,j),i\leq \lfloor\frac{n}d\rfloor,j\leq \lfloor\frac{m}d\rfloor}\mu(t)\\ =&amp;\sum_{d=1}^{\min(n,m)}d \cdot \sum_{t=1}^{\lfloor\frac{\min(n,m)}{d}\rfloor}t^2\mu(t)\cdot S(\lfloor\frac{n}{dt}\rfloor)S(\lfloor\frac{m}{dt}\rfloor)\\ =&amp;\sum_{T=1}^{\min(n,m)}S(\lfloor\frac{n}T\rfloor)S(\lfloor\frac{m}T\rfloor)T\sum_{d\mid T}d\mu(d) \end{aligned}

前面的已经可以 O ( 1 ) O(1) 求了,后面的这个东西看上去不是很好办: T d T d μ ( d ) T\sum_{d\mid T}d\mu(d)

F ( T ) = d T d μ ( d ) F(T)=\sum_{d\mid T}d\mu(d)

其实 F F 可以在线性筛的时候一起处理出来。

首先 F ( 1 ) = 1 F(1)=1

对于 p P , F ( p ) = 1 p p\in\mathbb P,F(p)=1-p

对于 p P , a N , p a , F ( a p ) = F ( a ) F ( p ) p\in\mathbb P,a\in \mathbb N_*,p\nmid a,F(ap)=F(a)F(p)

对于 p P , a N , p a , F ( a p ) = F ( a ) p\in \mathbb P,a\in \mathbb N_*,p\mid a,F(ap)=F(a)

这个通过新增一个质因子会产生的新的因子的贡献就可以看出了。

其实 F F 就是一个积性函数,这里就不证了。通过构造 F ( a b ) F(ab) F ( a ) × F ( b ) F(a)\times F(b) 的每一项的一一对应就行了。

[BZOJ2694]Lcm

传送门

解析:

首先这个东西我们必须要把它转化成式子不然没法推。

考虑利用莫比乌斯函数转化一下,我们要求的就是:
i = 1 n j = 1 m l c m ( i , j ) μ ( g c d ( i , j ) ) \sum_{i=1}^n\sum_{j=1}^mlcm(i,j)|\mu(gcd(i,j))|

注意上面 μ \mu 外面套的是绝对值符号。

我必须承认这个式子看着很鬼畜,但是只要不怕转化,就能化出来:

A n s = i = 1 n j = 1 m i j g c d ( i , j ) μ ( g c d ( i , j ) ) = d = 1 min ( n , m ) i = 1 n j = 1 m i j d μ ( d ) [ g c d ( i , j ) = d ] = d = 1 min ( n , m ) d μ ( d ) i = 1 n d j = 1 m d i j [ g c d ( i , j ) = 1 ] \begin{aligned} Ans=&amp;\sum_{i=1}^n\sum_{j=1}^m\frac{ij}{gcd(i,j)}|\mu(gcd(i,j))|\\ =&amp;\sum_{d=1}^{\min(n,m)}\sum_{i=1}^n\sum_{j=1}^m\frac{ij}d|\mu(d)|[gcd(i,j)=d]\\ =&amp;\sum_{d=1}^{\min(n,m)}d|\mu(d)|\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{m}d\rfloor}ij[gcd(i,j)=1] \end{aligned}

当出现形如 [ n = 1 ] [n=1] 的求和式子的时候,我们知道,可以开始反演了:

A n s = d = 1 min ( n , m ) d μ ( d ) i = 1 n d j = 1 m d i j t g c d ( i , j ) μ ( t ) \begin{aligned} Ans=&amp;\sum_{d=1}^{\min(n,m)}d|\mu(d)|\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}ij\sum_{t\mid gcd(i,j)}\mu(t) \end{aligned}

接下来用 S ( n ) S(n) 来表示 i = 1 n i \sum_{i=1}^ni ,显然上面的 i j ij 用结合律拆开后可以简化。得到:

A n s = d = 1 min ( n , m ) d μ ( d ) &ThinSpace; S ( n d ) S ( m d ) t g c d ( i , j ) , i n d , j m d μ ( t ) = d = 1 min ( n , m ) d μ ( d ) t = 1 min ( n , m ) d t 2 μ ( t ) S ( n d t ) S ( m d t ) \begin{aligned} Ans=&amp;\sum_{d=1}^{\min(n,m)}d|\mu(d)|\,S(\lfloor\frac{n}d\rfloor)S(\lfloor\frac{m}d\rfloor)\sum_{t\mid gcd(i,j),i\leq \lfloor\frac{n}d\rfloor,j\leq \lfloor\frac{m}{d}\rfloor}\mu(t)\\ =&amp;\sum_{d=1}^{\min(n,m)}d|\mu(d)|\sum_{t=1}^{\frac{\min(n,m)}{d}}t^2\mu(t)S(\lfloor\frac{n}{dt}\rfloor)S(\lfloor\frac{m}{dt}\rfloor) \end{aligned}

这个 d t dt 很麻烦,考虑直接枚举:

A n s = T = 1 min ( n , m ) S ( n T ) S ( m T ) &ThinSpace; T d T d μ ( d ) μ ( T d ) \begin{aligned} Ans=&amp;\sum_{T=1}^{\min(n,m)}S(\lfloor\frac{n}T\rfloor)S(\lfloor\frac{m}T\rfloor)\,T\sum_{d\mid T}d\mu(d)|\mu(\frac{T}d)| \end{aligned}

前面那一坨已经可以考虑整除分块了,但是我们要找到一个高效的方法维护这个东西的前缀和: g ( T ) = T d T d μ ( d ) μ ( T d ) g(T)=T\sum_{d\mid T}d\mu(d)|\mu(\frac{T}d)|

其实就是这个东西 g = I d ( ( I d μ ) μ ) g=Id\cdot ((Id\cdot \mu)*|\mu|)

我们知道积性函数的乘积和 D i r i c h l e t Dirichlet 卷积结果仍然是积性函数,换句话说, g g 是积性函数,可以在线性筛的时候一起处理。

那么首先显然有:
g ( 1 ) = 1 g(1)=1

p P , g ( p ) = p ( 1 p ) \forall p\in \mathbb P,g(p)=p(1-p) ,这个可以通过直接展开得到

p P , n N , g c d ( n , p ) = 1 , g ( p n ) = g ( p ) g ( n ) \forall p\in \mathbb P,n\in \mathbb N_*,gcd(n,p)=1,g(pn)=g(p)g(n) ,这个由积性函数性质得到。

那么现在需要考虑的就是 p P , n N , g c d ( n , p ) = p p\in \mathbb P,n\in \mathbb N_*,gcd(n,p)=p ,怎么求 g ( n p ) g(np)

首先,将 n n 分解: n = n × p s n=n&#x27;\times p^s

如果 s 2 s\geq 2 ,那么 n p np 中至少含有三个 p p 这个因子,那么这一项 μ ( d ) μ ( T d ) \mu(d)|\mu(\frac{T}d)| 就恒为 0 0 (根据抽屉原理),此时 g ( n p ) = 0 g(np)=0

不然, s = 1 s=1 ,我们可以得到: g ( n p ) = g ( n p ) g ( p 2 ) g(np)=g(\frac{n}p)g(p^2)

考虑怎么求 g ( p 2 ) g(p^2)

其实我们可以暴力展开:
g ( p 2 ) = p 2 ( 1 μ ( 1 ) μ ( p 2 ) + p μ ( p ) μ ( p ) + p 2 μ ( p 2 ) μ ( 1 ) ) = p 3 g(p^2)=p^2\cdot\big(1\cdot\mu(1)\cdot|\mu(p^2)|+p\cdot\mu(p)|\mu(p)|+p^2\cdot\mu(p^2)|\mu(1)|\big)=-p^3

这道题就做完了。

[洛谷P5176]公约数

求这个东西,对 1 e 9 + 7 1e9+7 取模

i = 1 n j = 1 m k = 1 p gcd ( i j , i k , j k ) × gcd ( i , j , k ) × ( gcd ( i , j ) gcd ( i , k ) × gcd ( j , k ) + gcd ( i , k ) gcd ( i , j ) × gcd ( j , k ) + gcd ( j , k ) gcd ( i , j ) × gcd ( i , k ) ) \sum_{i=1}^n\sum_{j=1}^m\sum_{k=1}^p\gcd(i\cdot j,i\cdot k,j\cdot k)\times \gcd(i,j,k)\times \left(\frac{\gcd(i,j)}{\gcd(i,k)\times \gcd(j,k)}+\frac{\gcd(i,k)}{\gcd(i,j)\times \gcd(j,k)}+\frac{\gcd(j,k)}{\gcd(i,j)\times \gcd(i,k)}\right)

每组数据中询问数不超过1000个,每组询问保证 1 0 7 n , m , p 2 × 1 0 7 10^7≤n,m,p≤2×10^7

解析:

最难受的就是那个 g c d ( i j , j k , i k ) gcd(ij,jk,ik) ,所以我们想办法把它给化简。

由于 g c d gcd 有一种表示方式是酱紫的:

a = p i a i , b = p i b i a=\prod p_i^{a_i},b=\prod p_i^{b_i}

g c d ( a , b ) = p i min ( a i , b i ) gcd(a,b)=\prod p_i^{\min(a_i,b_i)}

所以我们考虑分别每个质因子在 i , j , k i,j,k 中出现的次数,记为 I , J , K I,J,K

假设现在考虑且仅考虑质因子 p p
现在我们考虑给 g c d gcd 换一种表示方式,记录当前质因子的次数。

大概就是这么个意思:
g c d ( i , j ) = min ( I , J ) g c d ( j , k ) = min ( J , K ) g c d ( i , k ) = min ( I , K ) g c d ( i , j , k ) = min ( I , J , K ) g c d ( i j , i k , j k ) = min ( I + J , J + K , I + K ) \begin{aligned} gcd(i,j)&amp;=\min(I,J)\\ gcd(j,k)&amp;=\min(J,K)\\ gcd(i,k)&amp;=\min(I,K)\\ gcd(i,j,k)&amp;=\min(I,J,K)\\ gcd(ij,ik,jk)&amp;=\min(I+J,J+K,I+K) \end{aligned}

所以 g c d ( i j , i k , j k ) gcd(ij,ik,jk) 就是选择了 I , J , K I,J,K 三者中较小的两个。

容斥一下可以得到:

min ( I + J , J + K , I + K ) = min ( I , J ) + min ( J , K ) + min ( I , K ) min ( I , J , K ) \min(I+J,J+K,I+K)=\min(I,J)+\min(J,K)+\min(I,K)-\min(I,J,K)

很简单的容斥,就是说,在前三项中,次小值会出现一次,最小值会出现两次,最后一项会删掉重复出现的最小值,所以两边是相等的。

换回 g c d gcd 的表达式的话就是这个:
g c d ( i j , i k , j k ) = g c d ( i , j ) g c d ( i , k ) g c d ( j , k ) g c d ( i , j , k ) gcd(ij,ik,jk)=\frac{gcd(i,j)gcd(i,k)gcd(j,k)}{gcd(i,j,k)}

代回去得到我们真正要求的东西:
A n s = i = 1 n j = 1 m k = 1 p g c d ( i , j ) 2 + g c d ( i , k ) 2 + g c d ( j , k ) 2 Ans=\sum_{i=1}^n\sum_{j=1}^m\sum_{k=1}^{p}gcd(i,j)^2+gcd(i,k)^2+gcd(j,k)^2

将询问拆分一下: A n s = p i = 1 n j = 1 m g c d ( i , j ) 2 + n j = 1 m k = 1 p g c d ( j , k ) 2 + m i = 1 n k = 1 p g c d ( i , k ) 2 Ans=p\sum_{i=1}^{n}\sum_{j=1}^mgcd(i,j)^2+n\sum_{j=1}^m\sum_{k=1}^pgcd(j,k)^2+m\sum_{i=1}^n\sum_{k=1}^pgcd(i,k)^2

现在考虑怎么处理这个东西:

i = 1 n j = 1 m g c d ( i , j ) 2 \begin{aligned} \sum_{i=1}^n\sum_{j=1}^mgcd(i,j)^2 \end{aligned}

接下来是莫比乌斯反演的时间:
A n s = d = 1 min ( n , m ) d 2 i = 1 n j = 1 m [ g c d ( i , j ) = d ] = d = 1 min ( n , m ) d 2 i = 1 n j = 1 m t g c d ( i , j ) μ ( t ) = d = 1 min ( n , m ) d 2 t = 1 min ( n , m ) d n d t m d t = T = 1 min ( n , m ) n T m T d T d 2 μ ( T d ) \begin{aligned} Ans=&amp;\sum_{d=1}^{\min(n,m)}d^2\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)=d]\\ =&amp;\sum_{d=1}^{\min(n,m)}d^2\sum_{i=1}^n\sum_{j=1}^{m}\sum_{t\mid gcd(i,j)}\mu(t)\\ =&amp;\sum_{d=1}^{\min(n,m)}d^2\sum_{t=1}^{\lfloor\frac{\min(n,m)}{d}\rfloor}\lfloor\frac{n}{dt}\rfloor\lfloor\frac{m}{dt}\rfloor\\ =&amp;\sum_{T=1}^{\min(n,m)}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum_{d\mid T}d^2\mu(\frac{T}d) \end{aligned}

g ( T ) = d T d 2 μ ( T d ) g(T)=\sum_{d\mid T}d^2\mu(\frac{T}d)

很显然 g g 是个积性函数,因为 g = I d 2 μ g=Id^2*\mu

那么考虑线性筛。

g ( 1 ) = 1 g(1)=1

p P , g ( p ) = p 2 1 \forall p\in \mathbb P,g(p)=p^2-1

p P , n N , g c d ( n , p ) = 1 , g ( n p ) = g ( n ) g ( p ) \forall p\in \mathbb P,n\in \mathbb N_*,gcd(n,p)=1,g(np)=g(n)g(p)

p P , n N , p n , g ( n p ) = p 2 g ( n ) \forall p\in \mathbb P,n\in \mathbb N_*,p\mid n,g(np)=p^2g(n)

最后一个稍微考虑一下有哪些数会产生新的贡献大概就明白了。

[洛谷P4240]毒瘤之神的考验

传送门

解析:

首先我们有关于欧拉函数的结论:

ϕ ( i j ) = ϕ ( i ) ϕ ( j ) g c d ( i , j ) ϕ ( g c d ( i , j ) ) \phi(ij)=\frac{\phi(i)\phi(j)gcd(i,j)}{\phi(gcd(i,j))}

开始推导:

A n s = i = 1 n j = 1 m ϕ ( i j ) = i = 1 n j = 1 m ϕ ( i ) ϕ ( j ) g c d ( i , j ) ϕ ( g c d ( i , j ) ) = d = 1 min ( n , m ) d ϕ ( d ) i = 1 n j = 1 m ϕ ( i ) ϕ ( j ) [ g c d ( i , j ) = d ] = d = 1 min ( n , m ) d ϕ ( d ) i = 1 n d j = 1 m d ϕ ( i d ) ϕ ( j d ) [ g c d ( i , j ) = 1 ] = d = 1 min ( n , m ) d ϕ ( d ) i = 1 n d j = 1 m d ϕ ( i d ) ϕ ( j d ) t g c d ( i , j ) μ ( t ) = d = 1 min ( n , m ) d ϕ ( d ) t = 1 min ( n , m ) d μ ( t ) i = 1 n d t ϕ ( i d t ) j = 1 m d t ϕ ( j d t ) = T = 1 min ( n , m ) i = 1 n T ϕ ( i T ) j = 1 m T ϕ ( j T ) t T μ ( T t ) t ϕ ( t ) \begin{aligned} Ans&amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^m\phi(ij)\\ &amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^m\frac{\phi(i)\phi(j)gcd(i,j)}{\phi(gcd(i,j))}\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}\frac{d}{\phi(d)}\sum_{i=1}^n\sum_{j=1}^m\phi(i)\phi(j)[gcd(i,j)=d]\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}\frac{d}{\phi(d)}\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{m}d\rfloor}\phi(id)\phi(jd)[gcd(i,j)=1]\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}\frac{d}{\phi(d)}\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{m}d\rfloor}\phi(id)\phi(jd)\sum_{t\mid gcd(i,j)}\mu(t)\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}\frac{d}{\phi(d)}\sum_{t=1}^{\lfloor\frac{\min(n,m)}d\rfloor}\mu(t)\sum_{i=1}^{\lfloor\frac{n}{dt}\rfloor}\phi(idt)\sum_{j=1}^{\lfloor\frac{m}{dt}\rfloor}\phi(jdt)\\ &amp;=&amp;&amp;\sum_{T=1}^{\min(n,m)}\sum_{i=1}^{\lfloor\frac{n}T\rfloor}\phi(iT)\sum_{j=1}^{\lfloor\frac{m}{T}\rfloor}\phi(jT)\sum_{t\mid T}\mu(\frac{T}t)\frac{t}{\phi(t)} \end{aligned}

然后。。。优化不动了。。。

现在考虑预处理分段打表:

第一个函数:

F ( n ) = d n μ ( n d ) d ϕ ( d ) F(n)=\sum_{d\mid n}\mu(\frac{n}d)\frac{d}{\phi(d)}

通过枚举倍数可以在 O ( n log n ) O(n\log n) 时间内处理出来。

第二个函数:

G ( T , n ) = i = 1 n ϕ ( i T ) G(T,n)=\sum_{i=1}^n\phi(iT)

显然对于每一个 T T ,可能的 n n 只有 N T \frac{N}T 种,所以 G G 的总状态数只有 Θ ( n log n ) \Theta(n\log n) 个。

同时我们有 G ( T , n ) = G ( T , n 1 ) + ϕ ( n T ) G(T,n)=G(T,n-1)+\phi(nT) ,所以可以直接一波 O ( n log n ) O(n\log n) 处理完。

第三个函数:

S ( n , m , T ) = t = 1 T k t μ ( t k ) k ϕ ( k ) i = 1 n ϕ ( i t ) j = 1 m ϕ ( j t ) = t = 1 T F ( t ) G ( t , n ) G ( t , m ) \begin{aligned} S(n,m,T)&amp;=&amp;&amp;\sum_{t=1}^{T}\sum_{k\mid t}\mu(\frac{t}k)\frac{k}{\phi(k)}\sum_{i=1}^n\phi(it)\sum_{j=1}^m\phi(jt)\\ &amp;=&amp;&amp;\sum_{t=1}^TF(t)G(t,n)G(t,m) \end{aligned}

而显然 S ( n , m , T ) = S ( n , m , T 1 ) + F ( T ) G ( T , n ) G ( T , m ) S(n,m,T)=S(n,m,T-1)+F(T)\cdot G(T,n)\cdot G(T,m)

如果我们能够预处理出 S S ,就能够整除分块回答了。

但是显然这个空间是不可能开的下的。。。

但是我们知道,整除分块的前面一部分是连续的,这部分没有必要整除分块,可以直接计算,这就是分段打表的意义。

代码:

cs int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b>=mod?(ll)a*b%mod:(ll)a*b;}

cs int P=100005,B=35,N=P-5;
int inv[P],phi[P],mu[P];
int prime[P],pcnt;
bool mark[P];
int F[P],*G[P],*S[B+1][B+1];

inline void linear_sieves(int len=N){
    phi[1]=mu[1]=1;
    for(int re i=2;i<=N;++i){
        if(!mark[i])prime[++pcnt]=i,mu[i]=mod-1,phi[i]=i-1;
        for(int re j=1;i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j]){
                phi[i*prime[j]]=phi[i]*(prime[j]-1);
                mu[i*prime[j]]=dec(0,mu[i]);
            }
            else {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
        }
    }
}

inline void init(){
    inv[0]=inv[1]=1;
    for(int re i=2;i<=N;++i)
    inv[i]=mul(mod-mod/i,inv[mod%i]);
    linear_sieves();
    
    for(int re i=1;i<=N;++i)
    for(int re j=1;i*j<=N;++j)
    F[i*j]=add(F[i*j],mul(mu[j],mul(i,inv[phi[i]])));
    
    for(int re i=1;i<=N;++i){
        G[i]=new int[N/i+1];
        G[i][0]=0;
        for(int re j=1;j<=N/i;++j)
        G[i][j]=add(G[i][j-1],phi[i*j]);
    }
    
    for(int re i=1;i<=B;++i)
    for(int re j=1;j<=B;++j){
        int len=N/max(i,j);
        S[i][j]=new int[len+1];
        S[i][j][0]=0;
        for(int re k=1;k<=len;++k)
        S[i][j][k]=add(S[i][j][k-1],mul(F[k],mul(G[k][i],G[k][j])));
    }
}

inline int solve(int n,int m){
    if(n>m)swap(n,m);
    int ans=0;
    for(int re i=1;i<=m/B;++i)
    ans=add(ans,mul(F[i],mul(G[i][n/i],G[i][m/i])));
    for(int re i=m/B+1,j;i<=n;i=j+1){
        j=min(n/(n/i),m/(m/i));
        ans=add(ans,dec(S[n/i][m/i][j],S[n/i][m/i][i-1]));
    }
    return (ans%mod+mod)%mod;
}

int T;
signed main(){
    init();
    T=getint();
    while(T--)cout<<solve(getint(),getint())<<"\n";
    return 0;
}

[BZOJ3309]DZY Loves Math

传送门

解析:

定义 f ( n ) f(n) n n 的所有质因子次数中的最大值,求这个东西:

A n s = i = 1 n j = 1 m f ( g c d ( i , j ) ) Ans=\sum_{i=1}^n\sum_{j=1}^mf(gcd(i,j))

先反演:

A n s = i = 1 n j = 1 m f ( g c d ( i , j ) ) = d = 1 min ( n , m ) f ( d ) i = 1 n d j = 1 m d [ g c d ( i , j ) = d ] = d = 1 min ( n , m ) f ( d ) t = 1 min ( n , m ) d μ ( t ) n d t m d t = T = 1 min ( n , m ) n T m T d T f ( d ) μ ( T d ) \begin{aligned} Ans&amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^mf(gcd(i,j))\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}f(d)\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{m}d\rfloor}[gcd(i,j)=d]\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}f(d)\sum_{t=1}^{\lfloor\frac{\min(n,m)}{d}\rfloor}\mu(t)\lfloor\frac{n}{dt}\rfloor\lfloor\frac{m}{dt}{\rfloor}\\ &amp;=&amp;&amp;\sum_{T=1}^{\min(n,m)}\lfloor\frac{n}T\rfloor\lfloor\frac{m}T\rfloor\sum_{d\mid T}f(d)\mu(\frac{T}d) \end{aligned}

我们现在需要处理 g ( n ) = d n f ( d ) μ ( n d ) g(n)=\sum_{d\mid n}f(d)\mu(\frac{n}d) 的前缀和才能整除分块。

由于数据范围是 1 e 7 1e7 O ( n log n ) O(n\log n) 的预处理就只能放弃了。

但是现在大力分析一波:

  1. g ( 1 ) = 0 g(1)=0
  2. g ( p ) = f ( 1 ) μ ( p ) + f ( p ) μ ( 1 ) = 1 g(p)=f(1)\mu(p)+f(p)\mu(1)=1
  3. g ( p k ) = f ( p k 1 ) μ ( p ) + f ( p k ) μ ( 1 ) = 1 g(p^k)=f(p^{k-1})\mu(p)+f(p^k)\mu(1)=1
  4. g ( n ) = . . . g(n)=...

我们发现这种情况不是很好处理。

头铁分析一波。设 n n 的唯一分解式为 n = i = 1 t p i k i n=\prod_{i=1}^tp_i^{k_i} 。提取一个因子 d = i = 1 t p i q i d=\prod_{i=1}^tp_i^{q_i} 。如果存在 k i q i 2 k_i-q_i\geq 2 ,则该项的 μ \mu 为0,现在假设 i , k i q 1 1 \forall i,k_i-q_1\leq 1

显然 f ( d ) f(d) 只有两个取值: k m a x k_{max} k m a x 1 k_{max}-1

通过猜测并打表可以发现如下结论。

在这里,我给出以下引理并证明:

  1. 1 i , j t , k i = k j &ThickSpace; &ThickSpace; g ( n ) = ( 1 ) t + 1 \forall 1\leq i,j\leq t,k_i=k_j\iff g(n)=(-1)^{t+1}
  2. 1 i , j t , k i = ̸ k j &ThickSpace; &ThickSpace; g ( n ) = 0 \exist1\leq i,j\leq t,k_i=\not k_j\iff g(n)=0

我们先来证明引理 1 1 成立,再在引理 1 1 证明过程的基础上证明引理 2 2 的成立。

证明引理 1 1

先考虑 f ( d ) = k m a x 1 f(d)=k_{max}-1 的情况,显然只有一种可能,当且仅当 i , q i = k m a x 1 \forall i,q_i=k_{max}-1 ,这时候的贡献为 ( 1 ) t ( k m a x 1 ) (-1)^t(k_{max}-1)

来看 f ( d ) = k m a x f(d)=k_{max} 的情况,显然 2 t 2^t μ \mu 有值的情况剩下 2 t 1 2^{t}-1 种情况,这 2 t 1 2^{t}-1 种情况对应的就是 f ( d ) = k m a x f(d)=k_{max} ,利用二项式定理我们可以知道最后剩下的贡献就是 ( 1 ) t k m a x -(-1)^tk_{max}

于是最终的答案就是 ( 1 ) t + 1 (-1)^{t+1}

证明引理 2 2

首先,给一个 g ( n ) = 0 , f ( n ) = k g(n)=0,f(n)=k n n ,乘上一个 p s , p n , s k p^s,p\nmid n,s\leq k 之后是不会有任何改变的,这个可以通过构建前后卷积式子中 μ \mu 的一一对应来证明。

现在考虑给一个 g ( n ) = ̸ 0 , f ( n ) = k m a x g(n)=\not0,f(n)=k_{max} n n ,乘上一个 p s , p n , s &lt; k p^s,p\nmid n,s &lt; k

我们发现, p p 这一项在 d d 中是否出现不会对 f ( d ) f(d) 产生任何影响,它唯一的作用就是使得原来 n n 的卷积式子中的每一项多了一个乘上 p p 的选项。

于是每一项都有对应的相反数,直接两两求和就是 0 0 了。

代码实现可以考虑线性筛,维护最小质因子的次数和除去最小质因子剩下的数就可以 O ( 1 ) O(1) 转移了。

[SDOI2017]数字表格

传送门

解析:

显然 f f 数组是可以预处理出来的。

我们没有办法去对 f f 数组做太多变形。

现在需要求这个东西:

A n s = i = 1 n i = 1 m f ( g c d ( i , j ) ) Ans=\prod_{i=1}^{n}\prod_{i=1}^mf(gcd(i,j))

这个乘法看着很窒息,但是现在没有办法,先推式子:

A n s = d = 1 min ( n , m ) f ( d ) i = 1 n d j = 1 m d [ g c d ( i , j ) = 1 ] \begin{aligned} Ans&amp;=&amp;&amp;\prod_{d=1}^{\min(n,m)}f(d)^{\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}d\rfloor}[gcd(i,j)=1]} \end{aligned}

指数可以直接反演了,这个式子我们很熟悉,就是:

t = 1 min ( n , m ) d μ ( t ) n d t m d t \sum_{t=1}^{\lfloor\frac{\min(n,m)}{d}\rfloor}\mu(t)\lfloor\frac{n}{dt}\rfloor\lfloor\frac{m}{dt}\rfloor

套路,令 T = d t T=dt ,交换枚举顺序。

A n s = T = 1 min ( n , m ) d T f ( d ) n T m T μ ( T d ) = T = 1 min ( n , m ) ( d T f ( d ) μ ( T d ) ) n T m T \begin{aligned} Ans&amp;=&amp;&amp;\prod_{T=1}^{\min(n,m)}\prod_{d\mid T}f(d)^{\lfloor\frac{n}T\rfloor\lfloor\frac{m}T\rfloor\mu(\frac{T}d)}\\ &amp;=&amp;&amp;\prod_{T=1}^{\min(n,m)}(\prod_{d\mid T}f(d)^{\mu(\frac{T}d)})^{\lfloor\frac{n}T\rfloor\lfloor\frac{m}{T}\rfloor} \end{aligned}

调和级数复杂度预处理,维护前缀积及逆元,整除分块,这道题就做完了。

代码:

cs int mod=1e9+7;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}
inline int quickpow(int a,int b){
    re int res=1;
    while(b){
        if(b&1)res=mul(res,a);
        a=mul(a,a);
        b>>=1;
    }
    return res;
}
inline int inv(int a){return quickpow(a,mod-2);}

cs int P=1000006;
int prime[P],pcnt,mu[P];
bool mark[P];
int F[P],G[P];
int f[P],g[P];

inline void linear_sieves(int len=P-6){
    f[1]=g[1]=F[1]=F[0]=G[1]=G[0]=mu[1]=1;
    for(int re i=2;i<=len;++i){
        f[i]=add(f[i-1],f[i-2]);
        g[i]=inv(f[i]);
        F[i]=G[i]=1;
        if(!mark[i])prime[++pcnt]=i,mu[i]=-1;
        for(int re j=1;i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j]==0)break;
            mu[i*prime[j]]=-mu[i];
        }
    }
    for(int re i=1;i<=len;++i){
        if(!mu[i])continue;
        for(int re j=i;j<=len;j+=i)
        F[j]=mul(F[j],mu[i]==1?f[j/i]:g[j/i]),
        G[j]=mul(G[j],mu[i]==1?g[j/i]:f[j/i]);
    }
    for(int re i=2;i<=len;++i)F[i]=mul(F[i],F[i-1]),G[i]=mul(G[i],G[i-1]);
}

inline int solve(int n,int m){
    if(n>m)swap(n,m);
    int ans=1;
    for(int re i=1,j;i<=n;i=j+1){
        j=min(n/(n/i),m/(m/i));
        ans=mul(ans,quickpow(mul(F[j],G[i-1]),(ll)(n/i)*(m/i)%(mod-1)));
    }
    return (ans%mod+mod)%mod;
}

int T;
signed main(){
    linear_sieves();
    T=getint();
    while(T--)cout<<solve(getint(),getint())<<"\n";
    return 0;
}

[NOI2016]循环之美

传送门

解析:

现在是没有任何式子的,需要我们去找式子。

根据 小学奥数知识 接下来的推理我们可以得到一个式子。

首先在 k k 进制下,如果 x y \frac{x}{y} 是一个纯循环小数,则 x k l y \frac{xk^l}y 也是纯循环小数,反之亦然,其中 l l 是任意正整数。

这个定理是自冾的,所以不要问我怎么证明。

l l x y \frac{x}y k k 进制下的循环节长度,则 ( x y ) = ( x k l y ) (\frac{x}y)=(\frac{xk^l}y) ,其中 ( a ) (a) 表示 a a 的小数部分。

现在有:

x y x y = x k l y x k l y x x y y = x k l x k l y y x x k l ( m o d y ) \begin{aligned} \frac{x}y-\lfloor\frac{x}y\rfloor&amp;=&amp;\frac{xk^l}y-\lfloor\frac{xk^l}y\rfloor\\ x-\lfloor\frac{x}y\rfloor y&amp;=&amp;xk^l-\lfloor\frac{xk^l}{y}\rfloor y\\ x&amp;\equiv&amp;x k^l\pmod y \end{aligned}

熟悉取模操作的可以很简单的得到上面的结论。

由于我们只统计最简分数, g c d ( x , y ) = 1 gcd(x,y)=1

所以: k l 1 ( m o d y ) k^l\equiv 1\pmod y

l l 有解时出现纯循环,则 g c d ( k , y ) = 1 gcd(k,y)=1

则我们需要计算的东西就是:

A n s = i = 1 n j = 1 m [ g c d ( i , j ) = 1 ] [ g c d ( j , k ) = 1 ] Ans=\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1][gcd(j,k)=1]

开始推导,我们考虑对后面一个 g c d gcd 进行反演:

A n s = i = 1 n j = 1 m [ g c d ( i , j ) = 1 ] [ g c d ( j , k ) = 1 ] = i = 1 n j = 1 m [ g c d ( i , j ) = 1 ] d g c d ( j , k ) μ ( d ) = d k μ ( d ) i = 1 n j = 1 m d [ g c d ( i , j d ) = 1 ] = d k μ ( d ) i = 1 n j = 1 m d [ g c d ( i , j ) = 1 ] [ g c d ( i , d ) = 1 ] \begin{aligned} Ans&amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1][gcd(j,k)=1]\\ &amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1]\sum_{d\mid gcd(j,k)}\mu(d)\\ &amp;=&amp;&amp;\sum_{d\mid k}\mu(d)\sum_{i=1}^n\sum_{j=1}^{\lfloor\frac{m}d\rfloor}[gcd(i,jd)=1]\\ &amp;=&amp;&amp;\sum_{d\mid k}\mu(d)\sum_{i=1}^n\sum_{j=1}^{\lfloor\frac{m}d\rfloor}[gcd(i,j)=1][gcd(i,d)=1] \end{aligned}

我们发现,最终得到的式子和上面的形式是一样的,设

f ( n , m , k ) = i = 1 n j = 1 m [ g c d ( i , j ) = 1 ] [ g c d ( j , k ) = 1 ] f(n,m,k)=\sum_{i=1}^{n}\sum_{j=1}^m[gcd(i,j)=1][gcd(j,k)=1]

显然这个就可以 递归求解了:

f ( n , m , k ) = d k μ ( d ) f ( m d , n , d ) f(n,m,k)=\sum_{d\mid k}\mu(d)f(\lfloor\frac{m}d\rfloor,n,d)

递归边界有三个:

  1. f ( 0 , m , k ) = 0 f(0,m,k)=0
  2. f ( n , 0 , k ) = 0 f(n,0,k)=0
  3. f ( n , m , 1 ) = i = 1 n j = 1 m [ g c d ( i , j ) = 1 ] f(n,m,1)=\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1]

于是就可以记忆化搜索来解决这个问题了。

代码:

cs int P=1000006,N=P-6;
int prime[P],pcnt;
bool mark[P];
int mu[P],Summu[P];

inline void linear_sieves(int len=N){
    Summu[1]=mu[1]=1;
    for(int re i=2;i<=len;++i){
        if(!mark[i])prime[++pcnt]=i,mu[i]=-1;
        for(int re j=1;i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j]==0)break;
            mu[i*prime[j]]=-mu[i];
        }
        Summu[i]=mu[i]+Summu[i-1];
    }
}

tr1::unordered_map<int,int> ma;
inline int Sum(int n){
    if(n<=N)return Summu[n];
    if(ma.find(n)!=ma.end())return ma[n];
    int ans=1;
    for(int re i=2,j;i<=n;i=j+1){
        j=n/(n/i);
        ans-=(j-i+1)*Sum(n/i);
    }
    return ma[n]=ans;
}

int factor[30],fcnt;
struct data{
    int n,m,k;
    data(cs int _n,cs int _m,cs int _k):n(_n),m(_m),k(_k){}
    friend bool operator<(cs data &a,cs data &b){
        return (a.n^b.n)?(a.n<b.n):(a.m^b.m?(a.m<b.m):(a.k<b.k));
    }
};

map<data,ll> ans;
inline ll solve(int n,int m,int k){
    if(!n||!m)return 0;
    data now=data(n,m,k);
    if(ans.find(now)!=ans.end())return ans[now];
    if(k==1){
        if(n>m)swap(n,m);
        ll res=0;
        for(int re i=1,j;i<=n;i=j+1){
            j=min(n/(n/i),m/(m/i));
            res+=(ll)(Sum(j)-Sum(i-1))*(n/i)*(m/i);
        }
        return ans[now]=res;
    }
    ll res=0;
    for(int re i=1;i<=fcnt&&factor[i]<=k;++i)
    if(k%factor[i]==0)res+=mu[factor[i]]*solve(m/factor[i],n,factor[i]);
    return ans[now]=res;
}

int n,m,k;
signed main(){
    linear_sieves();
    cin>>n>>m>>k;
    for(int re i=1;i<=k;++i)if(mu[i]&&k%i==0)factor[++fcnt]=i;
    cout<<solve(n,m,k);
    return 0;
}

[BZOJ3561]DZY Loves Math VI

传送门

解析:

首先,这道题只有一组询问。

只有一组询问的gcd一般复杂度都是套一个调和级数级别的。

因为这时候允许枚举整除分块下的东西来计算。

还是先化式子,我们要求的是这个:

A n s = i 1 n j = 1 m l c m ( i , j ) g c d ( i , j ) Ans=\sum_{i-1}^n\sum_{j=1}^mlcm(i,j)^{gcd(i,j)}

开始推导:

A n s = d = 1 min ( n , m ) i = 1 n d i = 1 m d ( i j d ) d [ g c d ( i , j ) = 1 ] = d = 1 min ( n , m ) d d t = 1 n d μ ( t ) i = 1 n d t j = 1 m d t ( i j t 2 ) d = d = 1 min ( n , m ) d d t = 1 min ( n , m ) d μ ( t ) t 2 d i = 1 n d t i d j = 1 m d t j d \begin{aligned} Ans&amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{i=1}^{\lfloor\frac{m}d\rfloor}(ijd)^d[gcd(i,j)=1]\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}d^d\sum_{t=1}^{\lfloor\frac{n}{d}\rfloor}\mu(t)\sum_{i=1}^{\lfloor\frac{n}{dt}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{dt}\rfloor}(ijt^2)^d\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}d^d\sum_{t=1}^{\lfloor\frac{\min(n,m)}d\rfloor}\mu(t)t^{2d}\sum_{i=1}^{\lfloor\frac{n}{dt}\rfloor}i^d\sum_{j=1}^{\lfloor\frac{m}{dt}\rfloor}j^d \end{aligned}

不用化了,现在已经可以 O ( n log n ) O(n\log n) 求解了。

枚举 d d d d d^d 可以直接快速幂。

剩下的只需要维护前 m d \lfloor\frac{m}d\rfloor 个数的 d d 次方以及 d d 次方的前缀和就行了,这个可以在从小到大枚举 d d 的同时维护。

cs int mod=1000000007;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}
inline int quickpow(int a,int b){
	re int res=1;
	while(b){
		if(b&1)res=mul(res,a);
		a=mul(a,a);
		b>>=1;
	}
	return res;
}

cs int P=500005;
int mu[P],prime[P],pcnt;
bool mark[P];
inline void linear_sieves(int len=P-5){
	mu[1]=1;
	for(int re i=2;i<=len;++i){
		if(!mark[i])prime[++pcnt]=i,mu[i]=mod-1;
		for(int re j=1;i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0)break;
			mu[i*prime[j]]=dec(0,mu[i]);
		}
	}
}

int pows[P],sum[P];

int n,m;
int ans;
signed main(){
	cin>>n>>m;
	if(n>m)swap(n,m);
	linear_sieves(n);
	for(int re i=1;i<=m;++i)pows[i]=1;
	for(int re d=1;d<=n;++d){
		int now=0;
		for(int re t=1;t<=m/d;++t)pows[t]=mul(pows[t],t),sum[t]=add(sum[t-1],pows[t]);
		for(int re t=1;t<=n/d;++t)now=add(now,mul(mul(mu[t],mul(pows[t],pows[t])),mul(sum[n/d/t],sum[m/d/t])));
		ans=add(ans,mul(now,quickpow(d,d)));
	}
	cout<<ans;
	return 0;
}

[BZOJ4174]tty的求助

传送门

解析:

给出 n , m n,m

n = 1 N m = 1 M k = 0 m 1 n k + x m \sum_{n=1}^N\sum_{m=1}^M\sum_{k=0}^{m-1}\lfloor\frac{nk+x}m\rfloor

其中 x x 是实数。一组询问。

如果有神仙用类欧做出来这道题的话欢迎与我分享。

这是我唯一一道草稿打了超过一页的莫反题。当然它长得就很不像莫反题。

首先我们化简后面这个东西:

k = 0 m 1 n k + x m = k = 0 m 1 ( n k n k % m m + n k % m + x m ) = k = 0 m 1 n k m k = 0 m 1 n k % m m + k = 0 m 1 n k % m + x m \begin{aligned} \sum_{k=0}^{m-1}\lfloor\frac{nk+x}m\rfloor&amp;=&amp;&amp;\sum_{k=0}^{m-1}(\frac{nk-nk\%m}{m}+\lfloor\frac{nk\%m+x}m\rfloor)\\ &amp;=&amp;&amp;\sum_{k=0}^{m-1}\frac{nk}m-\sum_{k=0}^{m-1}\frac{nk\%m}m+\sum_{k=0}^{m-1}\lfloor\frac{nk\%m+x}{m}\rfloor \end{aligned}

d = g c d ( n , m ) d=gcd(n,m) ,显然 n k % m nk\%m 将这个数列取了 d d 遍: 0 , d , 2 d , 3 d . . . m d 0,d,2d,3d...m-d

于是我们有:

k = 0 m 1 n k m = n ( m 1 ) 2 k = 0 m 1 n k % m m = d k = 0 m d 1 k d m = d ( m d 1 ) 2 = m d 2 k = 0 m 1 n k % m + x m = d k = 0 m d 1 k d + x m = d k = 0 m d 1 k m d + x m = d m d x m = d x d \begin{aligned} &amp;\sum_{k=0}^{m-1}\frac{nk}m&amp;&amp;=&amp;&amp;\frac{n(m-1)}2\\ \end{aligned}\\ \begin{aligned} &amp;\sum_{k=0}^{m-1}\frac{nk\%m}{m}&amp;&amp;=&amp;&amp;d\sum_{k=0}^{\frac{m}d-1}\frac{kd}{m}\\ &amp;&amp;&amp;=&amp;&amp;\frac{d(\frac{m}d-1)}{2}\\ &amp;&amp;&amp;=&amp;&amp;\frac{m-d}{2}\\ \end{aligned}\\ \begin{aligned} &amp;\sum_{k=0}^{m-1}\lfloor\frac{nk\%m+x}{m}\rfloor&amp;&amp;=&amp;&amp;d\sum_{k=0}^{\frac{m}d-1}\lfloor\frac{kd+x}{m}\rfloor\\ &amp;&amp;&amp;=&amp;&amp;d\sum_{k=0}^{\frac{m}{d}-1}\lfloor\frac{k}{\frac{m}d}+\frac{x}m\rfloor\\ &amp;&amp;&amp;=&amp;&amp;d\lfloor\frac{m}d\cdot\frac{x}{m}\rfloor\\ &amp;&amp;&amp;=&amp;&amp;d\lfloor\frac{x}d\rfloor \end{aligned}

这一步是怎么来的呢?

d k = 0 m d 1 k m d + x m = d m d x m d\sum_{k=0}^{\frac{m}{d}-1}\lfloor\frac{k}{\frac{m}d}+\frac{x}m\rfloor=d\lfloor\frac{m}d\cdot\frac{x}{m}\rfloor

根据《具体数学》上的黑科技: k = 0 m 1 k m + x = m x \sum\limits_{k=0}^{m-1}\lfloor\frac{k}m+x\rfloor=\lfloor mx\rfloor

所以平时还是要多读一些课外读物

这个要证明的话直接讨论一下 k m + x \lfloor\frac{k}{m}+x\rfloor 在什么时候改变就行了,显然最多只会改变一次,而且是变成原数+1。这里不作过多阐述。

所以我们现在有:

k = 0 m 1 n k + x m = n ( m 1 ) 2 m d 2 + d x d = n m n m + d 2 + d x d \begin{aligned} \sum_{k=0}^{m-1}\lfloor\frac{nk+x}m\rfloor&amp;=&amp;&amp;\frac{n(m-1)}{2}-\frac{m-d}{2}+d\lfloor\frac{x}d\rfloor\\ &amp;=&amp;&amp;\frac{nm-n-m+d}{2}+d\lfloor\frac{x}d\rfloor \end{aligned}

换句话说,我们可以得到

A n s = n = 1 N m = 1 M n m n m + g c d ( n , m ) 2 + g c d ( n , m ) x g c d ( n , m ) = 1 2 n = 1 N m = 1 M ( n m n m ) + 1 2 n = 1 N m = 1 M g c d ( n , m ) + n = 1 N m = 1 M g c d ( n , m ) x g c d ( n , m ) \begin{aligned} Ans&amp;=&amp;&amp;\sum_{n=1}^N\sum_{m=1}^M\frac{nm-n-m+gcd(n,m)}{2}+gcd(n,m)\lfloor\frac{x}{gcd(n,m)}\rfloor\\ &amp;=&amp;&amp;\frac{1}2\sum_{n=1}^N\sum_{m=1}^M(nm-n-m)+\frac{1}2\sum_{n=1}^N\sum_{m=1}^Mgcd(n,m)+\\&amp;&amp;&amp;\sum_{n=1}^N\sum_{m=1}^Mgcd(n,m)\lfloor\frac{x}{gcd(n,m)}\rfloor \end{aligned}

我们发现第一部分可以 O ( 1 ) O(1) 求解,第二部分是一个经典的莫比乌斯反演题目,第三部分和第二部分相差无几。

对第二部分进行反演:

i = 1 n j = 1 m g c d ( i , j ) = d = 1 min ( n , m ) d i = 1 n d j = 1 m d [ g c d ( i , j ) = 1 ] = d = 1 min ( n , m ) d t = 1 n d μ ( t ) n d t m d t = T = 1 min ( n , m ) n T n T d T d μ ( d ) \begin{aligned} \sum_{i=1}^n\sum_{j=1}^mgcd(i,j)&amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}d\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{m}d\rfloor}[gcd(i,j)=1]\\ &amp;=&amp;&amp;\sum_{d=1}^{\min(n,m)}d\sum_{t=1}^{\lfloor\frac{n}{d}\rfloor}\mu(t)\lfloor\frac{n}{dt}\rfloor\lfloor\frac{m}{dt}\rfloor\\ &amp;=&amp;&amp;\sum_{T=1}^{\min(n,m)}\lfloor\frac{n}{T}\rfloor\lfloor\frac{n}T\rfloor\sum_{d\mid T}d\mu(d) \end{aligned}

第三部分的显然可以直接由上面的类似得到:

i = 1 n j = 1 m g c d ( i , j ) x g c d ( i , j ) = T = 1 min ( n , m ) n T m T d T d μ ( d ) x d \sum_{i=1}^n\sum_{j=1}^mgcd(i,j)\lfloor\frac{x}{gcd(i,j)}\rfloor=\sum_{T=1}^{\min(n,m)}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}T\rfloor\sum_{d\mid T}d\mu(d)\lfloor\frac{x}d\rfloor

形式上完全一样,可以选择一起处理,处理 O ( n log n ) O(n\log n) 调和级数就行了,不需要线性筛。

答案就是三部分加起来得到的结果。

[BZOJ3944]Sum

传送门

解析:

杜教筛核心式子:

g ( 1 ) S ( n ) = i = 1 n ( f g ) ( i ) i = 2 n g ( d ) S ( n d ) g(1)S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(d)S(\lfloor\frac{n}d\rfloor)

其中 f f 是指定的积性函数, S ( n ) = i = 1 n f ( i ) S(n)=\sum_{i=1}^nf(i)

现在到这道题里面看一看,我们需要求 ϕ \phi μ \mu 的前缀和。

1.我们要求的是 S ( n ) = i = 1 n ϕ ( n ) S(n)=\sum_{i=1}^{n}\phi(n)

D i r i c h l e t Dirichlet 卷积基础知识: ϕ 1 = I d \phi*1=Id

所以设 f = ϕ , g = 1 , f g = I d f=\phi,g=1,f*g=Id

直接得到:

S ( n ) = n ( n + 1 ) 2 i = 2 n S ( n i ) S(n)=\frac{n(n+1)}2-\sum_{i=2}^nS(\lfloor\frac{n}i\rfloor)

2.我们要求的是: S ( n ) = i = 1 n ϕ ( n ) S(n)=\sum_{i=1}^n\phi(n)

D i r i c h l e t Dirichlet 卷积基础知识: μ 1 = ϵ \mu*1=\epsilon

f = μ , g = 1 , f g = ϵ f=\mu,g=1,f*g=\epsilon

得到:

S ( n ) = 1 i = 2 n S ( n i ) S(n)=1-\sum_{i=2}^nS(\lfloor\frac{n}i\rfloor)

然后讲几个这道题的坑点。

洛谷上面有点卡常。

BZOJ上面有 n = 0 n=0 n = 2 31 1 n=2^{31}-1 的数据,记得要处理一下。

代码:

tr1::unordered_map<int,int> Summu;
tr1::unordered_map<int,ll> Sumphi;
cs int P=8000007,N=P-7,INF=0x7fffffff;
int prime[P],pcnt;
bool mark[P]; 
int mu[P];
ll phi[P];
inline void linear_sieves(int len=N){
    phi[1]=mu[1]=1;
    for(int re i=2;i<=len;++i){
        if(!mark[i]){
            prime[++pcnt]=i;
            phi[i]=i-1;
            mu[i]=-1;
        }
        for(int re j=1;i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];
                mu[i*prime[j]]=0;
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
            mu[i*prime[j]]=-mu[i];
        }
        mu[i]+=mu[i-1];
        phi[i]+=phi[i-1];
    }
}

inline int Get_summu(int n){
    if(n<=N)return mu[n];
    if(Summu[n])return Summu[n];
    int ans=1;
    for(int re i=2,j;j<INF&&i<=n;i=j+1){
        j=n/(n/i);
        ans-=(j-i+1)*Get_summu(n/i);
    }
    return Summu[n]=ans;
}

inline ll Get_sumphi(int n){
    if(n<=N)return phi[n];
    if(Sumphi[n])return Sumphi[n];
    ll ans=n*((ll)n+1)/2;
    for(int re i=2,j;j<INF&&i<=n;i=j+1){
        j=n/(n/i);
        ans-=(j-i+1)*Get_sumphi(n/i);
    }
    return Sumphi[n]=ans;
}

int T;
int n;
signed main(){
    linear_sieves();
    for(scanf("%d",&T);T--;){
        scanf("%d",&n);
        cout<<Get_sumphi(n)<<" "<<Get_summu(n)<<"\n";
    }
    return 0;
}

几个杜教筛能够处理的式子

由于懒得放题了,这里直接给几个式子,请各位自行尝试杜教筛,会给化简过程的。

(1) i = 1 n ϕ ( i ) i \sum_{i=1}^n\phi(i)\cdot i

f = I d ϕ , g = I d f=Id\cdot \phi,g=Id ,则有:

( f g ) ( n ) = d n ϕ ( d ) d n d = n d n ϕ ( d ) = n 2 \begin{aligned} (f*g)(n)&amp;=&amp;&amp;\sum_{d\mid n}\phi(d)d\frac{n}d\\ &amp;=&amp;&amp;n\sum_{d\mid n}\phi(d)\\ &amp;=&amp;&amp;n^2 \end{aligned}

平方前缀和公式 i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \sum_{i=1}^{n}i^2=\frac{n(n+1)(2n+1)}{6}

(2) i = 1 n ϕ ( i ) i 2 \sum_{i=1}^n\phi(i)\cdot i^2

f = I d 2 ϕ , g = I d 2 f=Id^2\cdot \phi,g=Id^2 ,则有:

( f g ) ( n ) = d n ϕ ( d ) d 2 ( n d ) 2 = n 2 d n ϕ ( d ) = n 3 \begin{aligned} (f*g)(n)&amp;=&amp;&amp;\sum_{d\mid n}\phi(d)d^2(\frac{n}d)^2\\ &amp;=&amp;&amp;n^2\sum_{d\mid n}\phi(d)\\ &amp;=&amp;&amp;n^3 \end{aligned}

立方前缀和公式: i = 1 n i 3 = ( i = 1 n i ) 2 = ( n ( n + 1 ) 2 ) 2 \sum_{i=1}^ni^3=(\sum_{i=1}^ni)^2=(\frac{n(n+1)}{2})^2

[洛谷P3768]简单的数学题

传送门

首先来头铁推式子。

我们要求的是这个:

i = 1 n j = 1 n i j g c d ( i , j ) \sum_{i=1}^n\sum_{j=1}^nijgcd(i,j)

化简过程如下,设 S ( n ) = i = 1 n i S(n)=\sum_{i=1}^ni

A n s = d = 1 n d i = 1 n j = 1 n i j [ g c d ( i , j ) = d ] = d = 1 n d 3 i = 1 n d j = 1 n d i j [ g c d ( i , j ) = 1 ] = d = 1 n d 3 t = 1 n d μ ( t ) t 2 S ( n t d ) 2 = T = 1 n S ( n T ) 2 d T d 3 ( T d ) 2 μ ( T d ) = T = 1 n S ( n T ) 2 T 2 d T d μ ( T d ) = T = 1 n S ( n T ) 2 T 2 ϕ ( T ) \begin{aligned} Ans&amp;=&amp;&amp;\sum_{d=1}^nd\sum_{i=1}^n\sum_{j=1}^nij[gcd(i,j)=d]\\ &amp;=&amp;&amp;\sum_{d=1}^nd^3\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{n}d\rfloor}ij[gcd(i,j)=1]\\ &amp;=&amp;&amp;\sum_{d=1}^nd^3\sum_{t=1}^{\lfloor\frac{n}d\rfloor}\mu(t)t^2S(\lfloor\frac{n}{td}\rfloor)^2\\ &amp;=&amp;&amp;\sum_{T=1}^nS(\lfloor\frac{n}T\rfloor)^2\sum_{d\mid T}d^3(\frac{T}d)^2\mu(\frac{T}d)\\ &amp;=&amp;&amp;\sum_{T=1}^nS(\lfloor\frac{n}T\rfloor)^2T^2\sum_{d\mid T}d\mu(\frac{T}d)\\ &amp;=&amp;&amp;\sum_{T=1}^nS(\lfloor\frac{n}T\rfloor)^2T^2\phi(T) \end{aligned}

所以可以整除分块了,考虑处理 I d 2 ϕ Id^2\cdot \phi 的前缀和,用上面提到的方法。

莫反套杜教筛的题目代码也只放这一道了。

代码:

inline int quickpow(int a,int b,int mod){
    re int res=1;
    while(b){
        if(b&1)res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        b>>=1;
    }
    return res;
}

int mod;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int mul(int a,int b){return (ll)a*b>=mod?(ll)a*b%mod:(ll)a*b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}

int inv2,inv6;
ll n;
cs int P=5000006,N=P-6;
int prime[P],pcnt,f[P];
bool mark[P];
inline void linear_sieves(int len=N){
    f[1]=1;
    for(int re i=2;i<=len;++i){
        if(!mark[i])prime[++pcnt]=i,f[i]=i-1;
        for(int re j=1;i*prime[j]<=len;++j){
            mark[i*prime[j]]=true;
            if(i%prime[j])f[i*prime[j]]=mul(f[i],prime[j]-1);
            else {f[i*prime[j]]=mul(f[i],prime[j]);break;}
        }
    }
    for(int re i=2;i<=len;++i)f[i]=add(mul(f[i],mul(i,i)),f[i-1]);
}

struct Map{
    static cs int magic=189859;
    int val[magic];
    ll key[magic];
    Map(){memset(key,-1,sizeof key);}
    cs int &operator[](cs ll &k)cs{
        int h=k%magic;
        while((~key[h])&&(key[h]^k))h=(h+1)%magic;
        return val[h];
    }
    int &operator[](cs ll &k){
        int h=k%magic;
        while((~key[h])&&(key[h]^k))h=(h+1)%magic;
        if((key[h]^k)){key[h]=k;}
        return val[h];
    }
    bool find(cs ll &k){
        int h=k%magic;
        while((~key[h])&&(key[h]^k))h=(h+1)%magic;
        return key[h]==k;
    }
}sumf;

inline int Sum_1(ll n){n%=mod;return mul(mul(n,n+1),inv2);}
inline int Sum_2(ll n){n%=mod;return mul(mul(mul(n,n+1),add(n,n)+1),inv6);}

inline int Sum(ll n){
    if(n<=N)return f[n];
    if(sumf.find(n))return sumf[n];
    int ans=Sum_1(n);ans=mul(ans,ans);
    for(ll re i=2,j;i<=n;i=j+1){
        j=n/(n/i);
        ans=dec(ans,mul(Sum(n/i),dec(Sum_2(j),Sum_2(i-1))));
    }
    return sumf[n]=ans;
}

int ans;
signed main(){
    cin>>mod>>n;
    inv2=quickpow(2,mod-2,mod);
    inv6=quickpow(6,mod-2,mod);
    linear_sieves();
    for(ll re i=1,j;i<=n;i=j+1){
        j=n/(n/i);
        ans=add(ans,mul(mul(Sum_1(n/i),Sum_1(n/i)),dec(Sum(j),Sum(i-1))));
    }
    cout<<ans<<"\n";
    return 0;
}

[BZOJ4176]Lucas的数论

传送门

解析:

求如下式子:

A n s = i = 1 n j = 1 n d ( i j ) Ans=\sum_{i=1}^n\sum_{j=1}^nd(ij)

有性质

d ( i j ) = k i l j [ g c d ( k , l ) = 1 ] d(ij)=\sum_{k\mid i}\sum_{l\mid j}[gcd(k,l)=1]

开始推导:

A n s = i = 1 n j = 1 n k i l j [ g c d ( k , l ) = 1 ] = i = 1 n j = 1 n k i l j t k , t l μ ( t ) = t = 1 n μ ( t ) k = 1 n l = 1 n [ d g c d ( k , l ) ] n k n l = t = 1 n μ ( t ) k = 1 n t l = 1 m t n k t n l t = t = 1 n μ ( t ) ( k = 1 n t n k t ) 2 \begin{aligned} Ans&amp;=&amp;&amp;\sum_{i=1}^{n}\sum_{j=1}^n\sum_{k\mid i}\sum_{l\mid j}[gcd(k,l)=1]\\ &amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^n\sum_{k\mid i}\sum_{l\mid j}\sum_{t\mid k,t\mid l}\mu(t)\\ &amp;=&amp;&amp;\sum_{t=1}^n\mu(t)\sum_{k=1}^{n}\sum_{l=1}^n[d\mid gcd(k,l)]\lfloor \frac{n}k\rfloor\lfloor\frac{n}l\rfloor\\ &amp;=&amp;&amp;\sum_{t=1}^n\mu(t)\sum_{k=1}^{\lfloor\frac{n}t\rfloor}\sum_{l=1}^{\lfloor\frac{m}t\rfloor}\lfloor\frac{n}{kt}\rfloor\lfloor\frac{n}{lt}\rfloor\\ &amp;=&amp;&amp;\sum_{t=1}^{n}\mu(t)(\sum_{k=1}^{\lfloor\frac{n}t\rfloor}\lfloor\frac{n}{kt}\rfloor)^2 \end{aligned}

用杜教筛处理出 μ \mu 的前缀和,然后看这个东西:

i = 1 n n i \sum_{i=1}^n\lfloor\frac{n}{i}\rfloor

显然就是 d ( n ) d(n) 的前缀和,由于我们只需要求 O ( n ) O(\sqrt n) 种不同的 d ( n ) d(n) 前缀和,每个 d ( n ) d(n) 前缀和可以 O ( n ) O(\sqrt n) 求,可以直接每个 d ( n ) d(n) 前缀和根号求一下(总复杂度 O ( n 3 4 ) O(n^{\frac{3}4}) ),也可以效仿杜教筛预处理前 n 2 3 n^{\frac{2}3} 个(总复杂度 O ( n 2 3 ) O(n^{\frac{2}3}) )。

出门左转顺便水掉[SDOI2015]约数个数和,注意这道题是两个参数 n , m n,m

[51nod1220]约数之和

传送门

解析:

求如下式子的值:

A n s = i = 1 n j = 1 n σ ( i j ) Ans=\sum_{i=1}^n\sum_{j=1}^n\sigma(ij)

其中 σ ( n ) \sigma(n) 表示 n n 的所有约数和。

约数类的函数显然有相通之处,试着按照推 d ( i j ) d(ij) 的套路来推 σ ( i j ) \sigma(ij) 可以得到:

σ ( i j ) = k i l j i l k [ g c d ( l , k ) = 1 ] \sigma(ij)=\sum_{k\mid i}\sum_{l\mid j}\frac{il}k[gcd(l,k)=1]

证明就免了,和 d ( i j ) = k i l j [ g c d ( k , l ) = 1 ] d(ij)=\sum_{k\mid i}\sum_{l\mid j}[gcd(k,l)=1] 的套路是一样的。

开始推导:

A n s = i = 1 n j = 1 n σ ( i j ) = i = 1 n j = 1 n k i l j i l k [ g c d ( k , l ) = 1 ] = i = 1 n j = 1 n k i l j i l k d k , d l μ ( d ) = d = 1 n μ ( d ) i = 1 n j = 1 n k i l j [ d k ] [ d l ] i l k = d = 1 n d μ ( d ) i = 1 n d j = 1 n d k i l j i l k = d = 1 n d μ ( d ) i = 1 n d k i i k j = 1 n d l j l = d = 1 n d μ ( d ) ( k = 1 n d k n d k ) 2 \begin{aligned} Ans&amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^n\sigma(ij)\\ &amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^n\sum_{k\mid i}\sum_{l\mid j}\frac{il}{k}[gcd(k,l)=1]\\ &amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^n\sum_{k\mid i}\sum_{l\mid j}\frac{il}{k}\sum_{d\mid k,d\mid l}\mu(d)\\ &amp;=&amp;&amp;\sum_{d=1}^n\mu(d)\sum_{i=1}^n\sum_{j=1}^n\sum_{k\mid i}\sum_{l\mid j}[d\mid k][d\mid l]\frac{il}k\\ &amp;=&amp;&amp;\sum_{d=1}^nd\mu(d)\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{k\mid i}\sum_{l\mid j}\frac{il}{k}\\ &amp;=&amp;&amp;\sum_{d=1}^nd\mu(d)\sum_{i=1}^{\lfloor\frac{n}d\rfloor}\sum_{k\mid i}\frac{i}k\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{l\mid j}l\\ &amp;=&amp;&amp;\sum_{d=1}^nd\mu(d)(\sum_{k=1}^{\lfloor\frac{n}d\rfloor}k\lfloor\frac{n}{dk}\rfloor)^2 \end{aligned}

前面这个东西 d μ ( d ) d\mu(d) 是个积性函数,显然可以线性筛 考虑杜教筛。

f = I d μ f=Id\cdot \mu ,则我们找到 g = I d g=Id 来构造杜教筛。

( f g ) ( n ) = d n d μ ( d ) n d = n d n μ ( d ) = [ n = 1 ] = ϵ ( n ) \begin{aligned} (f*g)(n)&amp;=&amp;&amp;\sum_{d\mid n}d\mu(d)\frac{n}d\\ &amp;=&amp;&amp;n\sum_{d\mid n}\mu(d)\\ &amp;=&amp;&amp;[n=1]=\epsilon(n) \end{aligned}

于是得到杜教筛式子如下:

S ( n ) = 1 i = 2 n i S ( n i ) S(n)=1-\sum_{i=2}^niS(\lfloor\frac{n}i\rfloor)

后面这个东西:

i = 1 n i n i \sum_{i=1}^ni\lfloor\frac{n}i\rfloor

显然是 σ ( n ) \sigma(n) 的前缀和。考虑线性筛 仿照杜教筛预处理一部分,剩下的部分可以 O ( n 2 3 ) O(n^{\frac{2}3}) 时间内解决。

[51nod2026]Gcd and Lcm

传送门

解析:

定义 f = ( μ I d ) 1 f=(\mu\cdot Id)*1 ,求这个式子:

A n s = i = 1 n j = 1 n f ( g c d ( i , j ) ) f ( l c m ( i , j ) ) Ans=\sum_{i=1}^n\sum_{j=1}^nf(gcd(i,j))f(lcm(i,j))

对于积性函数 f f ,显然有

f ( g c d ( i , j ) ) f ( l c m ( i , j ) ) = f ( i ) f ( j ) f(gcd(i,j))\cdot f(lcm(i,j))=f(i)\cdot f(j)

对每个质因数分开考虑, g c d gcd 中的指数必然是 i , j i,j 中该质因子指数较小的,而 l c m lcm 中的指数必然是该质因子质数较大的那个,分解后合并显然可以得到 i , j i,j

所以直接可以得到

A n s = i = 1 n j = 1 n f ( i ) f ( j ) = ( i = 1 n f ( i ) ) 2 \begin{aligned} Ans&amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^nf(i)f(j)\\ &amp;=&amp;&amp;(\sum_{i=1}^nf(i))^2 \end{aligned}

现在求 i = 1 n t i μ ( t ) t \sum\limits_{i=1}^n\sum_{t\mid i}\mu(t)t

A n s = d = 1 n d μ ( d ) n d \begin{aligned} Ans&amp;=&amp;&amp;\sum_{d=1}^nd\mu(d)\lfloor\frac{n}{d}\rfloor \end{aligned}

杜教筛+整除分块水完

[51nod1238]最小公倍数之和V3

传送门

解析:

求:

A n s = i = 1 n j = 1 n l c m ( i , j ) Ans=\sum_{i=1}^n\sum_{j=1}^nlcm(i,j)

直接反演是不太好做的,考虑我们之前做过的LCM Sum,我们已经推导出了这个结论:

i = 1 n l c m ( i , n ) = n 2 d n ( d ϕ ( d ) + [ d = 1 ] ) \sum_{i=1}^nlcm(i,n)=\frac{n}2\sum_{d\mid n}(d\phi(d)+[d=1])

于是我们需要求的东西就可以化简了

A n s = i = 1 n j = 1 n l c m ( i , j ) = 2 i = 1 n j = 1 i l c m ( i , j ) i = 1 n i = 2 i = 1 n ( i 2 d i ( d ϕ ( d ) + [ d = 1 ] ) ) i = 1 n i = i = 1 n i d i d ϕ ( d ) = d = 1 n d 2 ϕ ( d ) i = 1 n d i \begin{aligned} Ans&amp;=&amp;&amp;\sum_{i=1}^n\sum_{j=1}^nlcm(i,j)\\ &amp;=&amp;&amp;2\sum_{i=1}^n\sum_{j=1}^ilcm(i,j)-\sum_{i=1}^ni\\ &amp;=&amp;&amp;2\sum_{i=1}^n(\frac{i}2\sum_{d\mid i}(d\phi(d)+[d=1]))-\sum_{i=1}^ni\\ &amp;=&amp;&amp;\sum_{i=1}^ni\sum_{d\mid i}d\phi(d)\\ &amp;=&amp;&amp;\sum_{d=1}^nd^2\phi(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}i \end{aligned}

现在就只需要用杜教筛处理出 I d 2 ϕ Id^2\cdot\phi 的前缀和,然后对后面的 i i 整除分块就行了。

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/86761985
今日推荐