├── .gitignore ├── 00Preface.tex ├── 01divisibility.tex ├── 02prime.tex ├── 03congruence.tex ├── 04residue.tex ├── 05productfunction.tex ├── 06indefiniteequation.tex ├── 07others.tex ├── LICENSE ├── README.md ├── app1.tex ├── code01 ├── exgcd.cpp └── gcd.cpp ├── code02 ├── aishi.cpp ├── euler.cpp ├── factor.cpp ├── interaishi.cpp ├── intereuler.cpp ├── large-inter-factor.cpp ├── large-inter-prime.cpp ├── maxprime.cpp └── minprime.cpp ├── code03 ├── bsgs.cpp ├── carmichael.cpp ├── crt.cpp ├── excrt.cpp ├── fastexp.cpp ├── fastmul.cpp ├── inverse.cpp ├── linearinverse.cpp ├── luogu-p4718.cpp ├── miller.cpp ├── modequation.cpp ├── o1fastmul.cpp ├── pollard-rho-with-multi.cpp ├── pollard-rho.cpp ├── primitive-root.cpp └── superlog.cpp ├── code04 ├── Kakin.cpp ├── Legendre.cpp ├── N-res-notprime.cpp ├── N-res.cpp └── quad-res.cpp ├── code05 ├── 18sichuanG.cpp ├── 51nod1239.cpp ├── 51nod1244.cpp ├── APS2.cpp ├── DIVCNTK.cpp ├── Factor-number-function-prefix.cpp ├── Neko-and-function.cpp ├── Sum-function-of-factors.cpp ├── cf757E.cpp ├── euler-example3.cpp ├── euler-linear.cpp ├── euler.cpp ├── function.cpp ├── mobi-HYSBZ2154.cpp ├── mobi-bzoj2820.cpp ├── mobi-linear.cpp ├── mobiwusi.cpp ├── nowcode-Function.cpp ├── nthprime.cpp ├── nthprime2.cpp ├── sum-of-primes.cpp └── varphi-mu.cpp ├── code06 └── OEIS-A011772.cpp ├── code07 ├── Anti-Euler.cpp ├── Lattice-points.cpp ├── NTT.cpp ├── counting-sequences.cpp ├── fft-iteration.cpp ├── fft-recursion.cpp ├── fraction2decimal.cpp ├── mostfactors.cpp ├── random-algo-modsqr.cpp └── redirect-cout.cpp ├── elegantbook-cn.tex ├── figure ├── Fast-and-slow-pointer.png ├── cover.jpg ├── cover2.jpg ├── fermat-descent.png ├── fft-iter.png ├── fft1.png ├── fft2.png ├── logo.png ├── ntt.png ├── prime-root.png └── quadratic-residue-example.png ├── image ├── donate.jpg ├── scatter.py ├── star-history.png ├── star.png └── tlshell.png ├── others ├── cover.py └── 二次剩余.xlsx ├── reference.bib └── 程序设计竞赛中的数论问题.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.aux 3 | 4 | 5 | 6 | *.out 7 | 8 | *.log 9 | 10 | *.gz 11 | 12 | *.toc 13 | 14 | *.bbl 15 | 16 | *.blg 17 | 18 | *.cls 19 | 20 | elegantbook-cn.pdf 21 | -------------------------------------------------------------------------------- /00Preface.tex: -------------------------------------------------------------------------------- 1 | \chapter*{写在前面} 2 | \addcontentsline{toc}{chapter}{写在前面} 3 | \markboth{Introduction}{} 4 | 参加ACM-ICPC已经快三年了,想着留下些什么,于是决定将数论方面的一些知识整理成册,供后人参考。 5 | 6 | 这本册子的目的是{\heiti 降低参赛同学们收集数论相关知识的时间成本},对知识点的介绍尽量做到详细且通俗易懂。 7 | 书中会对所有出现的题目提供solution,一些经典的定理、结论还会提供证明。 8 | 9 | 在册子完成过程中,AHU ICPC-Lab的同学们提出了许多宝贵意见,在此向他们表示衷心的感谢。此外,本书使用了ElegantBook模板 10 | (\href{https://github.com/ElegantLaTeX/}{https://github.com/ElegantLaTeX/}),减少了许多排版方面的工作,在此也表示感谢! 11 | 12 | 由于本人水平所限,书中一定存在不少错误,欢迎同学们在GitHub上提出issues或者邮件联系我。\\ 13 | 14 | \vbox{} 15 | 16 | 17 | 联系方式: 18 | \begin{itemize} 19 | \item GitHub 网址:\href{https://github.com/Feynman1999/number-theory-tutorial-in-Programming-Competition}{https://github.com/Feynman1999/number-theory-tutorial-in-Programming-Competition} 20 | \item 邮件:\email{chenyx.cs@gmail.com} 21 | \end{itemize} 22 | 23 | \vbox{} 24 | 25 | 本书的Tex源码开源在GitHub,未经本人许可不可用于商业用途。\\ 26 | 27 | \vbox{} 28 | 29 | \begin{flushright} 30 | 陈昱翔 31 | 32 | 2019年12月 33 | \end{flushright} 34 | -------------------------------------------------------------------------------- /01divisibility.tex: -------------------------------------------------------------------------------- 1 | \chapter{数的整除性} 2 | \begin{introduction}[本章内容提要] 3 | \item 欧几里得算法 4 | \item 拓展欧几里得算法 5 | \end{introduction} 6 | 7 | \section{最小公倍数} 8 | 两个数$a$与$b$(不全为零)的最大公因数是整除它们两个的最大数,记为$gcd(a,b)$。如果$gcd(a,b)=1$,我们称$a$与$b$互质。 9 | 10 | 求两个数最大公因数的最有效的方法是{\heiti 欧几里得算法},其核心操作是辗转相除,先看一个例子。 11 | 12 | \begin{example} 13 | 欧几里得算法求$gcd(28,93)$的例子。 14 | 15 | 第一步用93除以28得商为3,余数为7,记作下面式子: 16 | $$ 17 | 93 = 3*28 + 7 18 | $$ 19 | 第二步用上一步的除数作为新的被除数,上一步的余数作为新的除数,即: 20 | $$ 21 | 28 = 4*7 + 0 22 | $$ 23 | 发现此时余数为0,算法不再继续。欧几里得算法指出当得到余数0时,除数(上一步的余数)就是最初两个数的最大公因数。所以 24 | $gcd(28,93)$ = 7。 25 | \end{example} 26 | 27 | \begin{theorem}{欧几里得算法}{label} 28 | 要计算两个整数$a$与$b$的GCD,先令$r_{-1}=a$且$r_{0}=b$,然后计算相继的商和余数:$r_{i-1}=q_{i+1}*r_{i}+r_{i+1} \quad (i=0,1,2,...)$, 29 | 直到某余数$r_{n+1}=0$,最后的非零余数$r_{n}$就是$a$与$b$的最大公因数。 30 | \end{theorem} 31 | 32 | \begin{proof} 33 | 考虑一般情形,有 34 | \begin{align*} 35 | r_{-1} &= q_1*r_0 + r_1 \\ 36 | r_0 &= q_2*r_1 + r_2 \\ 37 | r_1 &= q_3 * r_2 + r_3 \\ 38 | r_2 &= q_4 * r_3 + r_4 \\ 39 | &\cdot \cdot \cdot\\ 40 | r_{n-3} &= q_{n-1}*r_{n-2} + r_{n-1} \\ 41 | r_{n-2} &= q_{n} * r_{n-1} + {\color{red} r_n} \\ 42 | r_{n-1} &= q_{n+1} * r_n + 0 \\ 43 | \end{align*} 44 | 欧几里得算法说,最后的$r_n$就是gcd,那么我们先证明$r_n$一定是$a(r_{-1})$和$b(r_0)$的因子。 45 | 46 | 由最后一行知,$r_n$整除$r_{n-1}$,于是由倒数第二行知$r_n$整除$r_{n-2}$,依次类推,$r_n$整除$r_{0}$和$r_{-1}$。 47 | 48 | 下面再证明$r_n$是$a$与$b$的{\heiti 最大}公因数。假设$d$是$a$与$b$的任意公因数,则由上面式子第一行知$d$整除$r_1$, 49 | 于是由第二行知$d$整除$r_2$,依次类推,$d$整除$r_n$,即$r_n$是最大的公因数。 50 | 51 | 最后,还要说明一定存在$r_{n+1}$为0,易知,每一次余数都是严格递减的,所以余数最后总能到0。那自然会问,要多少步呢? 52 | 实际上,{\heiti 欧几里得算法的步数至多是$b$的位数的7倍}。 53 | \end{proof} 54 | 55 | 56 | \lstinputlisting[language=C++, style=codestyle2]{code01/gcd.cpp} 57 | 58 | 说到GCD,往往还会提到最小公倍数(LCM),有如下式子 59 | \begin{align*} 60 | lcm(a,b) * gcd(a,b) &=a*b \\ 61 | lcm(S/a, S/b) &= S/gcd(a, b) 62 | \end{align*} 63 | 64 | \begin{note} 65 | 另外一种计算两个数最大公约数的算法,叫做{\heiti Stein算法}。这种算法是针对欧几里得算法在对大整数进行运算时,需要试商导致增加运算时间的缺陷而提出的改进算法。 66 | 感兴趣的可以自行查阅。 67 | \end{note} 68 | 69 | 70 | \section{线性方程定理} 71 | 72 | 已知两个整数$a,b$,现在我们观察由$a$的倍数加上$b$的倍数得到的所有可能整数,也就是说{\heiti 考察$ax+by$得到的所有整数},其中$x,y$可以是任何整数。 73 | 74 | 例如取$a=42$,$b=30$,随意列出一些$x,y$,会发现得到的数都可以被6整除。这其实很显然,因为42和30都能被6整除。更一般的,{\heiti 显然形如$ax+by$的每个数被$gcd(a,b)$整除},因为$gcd(a,b)$整除a与b。 75 | 76 | 接着问题又来了,$ax+by=gcd(a,b)$是否一定有整数解呢?答案是肯定的。证明是利用{\heiti 拓展欧几里得算法},不停的将余数用$a$和$b$表示,{\heiti 最后的$gcd(a,b)$一定可以用$a,b$表示}。也称欧几里得回代法。 77 | 78 | \begin{example} 79 | 欧几里得回代法解$60x+22y=gcd(60,22)$ 80 | 81 | 首先写出欧几里得算法计算$gcd(60,22)$的过程: 82 | \begin{align*} 83 | 60=2 * 22+16 \\ 84 | 22=1 * 16+6 \\ 85 | 16=2 * 6+4 \\ 86 | 6=1 * 4+2 \\ 87 | 4=2 * 2+0 88 | \end{align*} 89 | 90 | 这表明$gcd=2$。下面关键来了,我们想用$a,b$表示倒数第二行$6=1*4+2$的那个2,就从第一行表示$a,b$回代,如表\ref{tab:欧几里得回代法示例}: 91 | 92 | \begin{table}[!htbp] 93 | \centering 94 | \caption{欧几里得回代法示例} 95 | \begin{tabular}{cccc} 96 | \toprule 97 | 原式 && & 带入过程 \\ 98 | \midrule 99 | $a=2*b+16$&& & $16=a-2*b$ \\ 100 | $b=1*16+6$&& & $6=b-1*16=-a+3b$ \\ 101 | $16=2*6+4$&& & $4=16-2*6=3a-8b$ \\ 102 | $6=1*4+2$ && & $2=6-1*4=-4a+11b$ \\ 103 | \bottomrule 104 | \end{tabular}% 105 | \label{tab:欧几里得回代法示例} 106 | \end{table}% 107 | 108 | 于是得到$x=-4, \ y=11$。 109 | 110 | \end{example} 111 | 112 | {\heiti 这样一行行进行,将陆续得到形如最新余数=a的倍数+b的倍数的等式,最终得到非零余数gcd,就给出了方程的解。} 113 | 114 | \begin{note} 115 | $gcd(a,b)$是否是形如$ax+by$的最小正整数呢?是的。因为$gcd(a,b)<=min(a,b)$。 116 | \end{note} 117 | 118 | \vbox{} 119 | 120 | 既然一个线性方程$ax+by=gcd(a,b)$总有整数解$x,y$,下面的问题是{\heiti 如何表述线性方程的所有解}。 121 | 122 | 先考虑$gcd(a,b)=1$的情况(即$a,b$互质,其他情况可以转换为互质),假设$x_1,y_1$是方程$ax+by=1$的一个解。 123 | 通过$x_1$加上b的倍数和$y_1$减去a的相同倍数,可得到其他解。即对于任何整数$k$,我们得到新解$(x_{1}+kb,y_{1}-ka)$。(带入即可验证) 124 | 125 | \begin{proof} 126 | $(x_{1}+kb,y_{1}-ka)$可以给出方程的{\heiti 所有}解。 127 | 128 | 假设$(x_{1},y_{1})$与$(x_{2},y_{2})$是方程$ax+by=1$的两个解,即$ax_{1}+by_{1}=1$ 且$ ax_{2}+by_{2}=1$ ,我们用$y_{2}$乘以第一个方程,用$y_{1}$乘以第二个方程,再相减消去b,整理后得到$ax_{1}y_{2}-ax_{2}y_{1}=y_{2}-y_{1}$。 129 | 类似的,用$x_{2}$乘以第一个方程,用$x_{1}$乘以第二个方程,再相减便得到$bx_{2}y_{1}-bx_{1}y_{2}=x_{2}-x_{1}$。 130 | 令$k=x_{2}y_{1}-x_{1}y_{2}$,则得到$x_{2}=x_{1}+kb \ ,\ y_{2}=y_{1}-ka$。得证。 131 | \end{proof} 132 | 133 | \vbox{} 134 | 135 | 再考虑$gcd(a,b)>1$的情况,为简便,令$g=gcd(a,b)$,由前面“欧几里得回代法”知方程$ax+by=g$至少有一个解$(x_{1},y_{1})$。 136 | {\heiti 而$g$整除$a,b$},故$(x_{1},y_{1})$是简单方程$\frac{a}{g}x+\frac{b}{g}y=1$的解。 137 | 于是由前面证明,通过式子$x_1+k*\frac{b}{g},\ y_1-k*\frac{a}{g}$改变$k$的值可得到其他解。这就完成了对方程$ax+by=g$解的描述,我们把它概括为下面定理。 138 | 139 | 140 | \begin{theorem}{线性方程定理}{label} 141 | 设$a$与$b$是非零整数,$g=gcd(a,b)$。方程$ax+by=g$总是有一个整数解$(x_{1},y_{1})$(也称贝祖定理), 142 | 它可由前面叙述的欧几里得回带法得到。则方程的每一个解可由$(x_{1}+k*\frac{b}{g},y_{1}-k*\frac{a}{g})$得到,其中$k$可为任意整数。 143 | \end{theorem} 144 | 145 | \vbox{} 146 | 147 | 现在我们想怎么用代码实现上面说到的回代法。 148 | 149 | 如果用上面的思路,我们似乎需要事先知道欧几里得的结果,然后根据结果去算系数。 150 | 但是一般用计算机求解$gcd$,是递归算法,就是说我们需要逆推(上面过程的逆过程),这样才能写出递归代码。 151 | 152 | 假设当前我们要处理的是求出$a$ 和 $b$的最大公约数,并要求出 $x$ 和 $y$ 使得 $a * x + b * y=g$。 153 | 而我们已经求出了下一个状态:$b$ 和 $a\%b$ 的最大公约数({\heiti 欧几里得算法核心,$a=b,\ b=a\%b$}),并且求出了一组$x_1$ 和$y_1$ 使得:$ b * x_1 + (a\%b) * y_1 = g$。 154 | 那么这两个相邻的状态之间存在一种关系可以求解,我们知道$a\%b = a - (a/b)*b$,那么较末状态(先开始递归计算的)有: 155 | 156 | \begin{align*} 157 | b * x_1 + (a\%b) * y_1 &= g\\ 158 | b*x_{1} + (a-(a/b)*b) * y_{1} &= g \\ 159 | b*x_{1} + a*y_{1}-(a/b)*b*y_{1} &=g\\ 160 | a*y_{1} + b*(x_{1}-a/b*y_{1})&= g 161 | \end{align*} 162 | 对比较先的状态(后递归到的,需要求的),需要求一组 x 和 y 使得:$a*x + b*y = g$ ,比较对应项系数得到 163 | \begin{align*} 164 | x &= y_1\\ 165 | y &= x_1 - a/b * y_1 \\ 166 | \end{align*} 167 | 168 | 169 | 这就是递归的关键,我们把它称为{\heiti 拓展欧几里得算法}。之所以叫拓展,是因为它相比欧几里得算法多了一小点,但能解决模线性方程、逆元、同余方程等许多经典问题(后面会介绍)。 170 | 171 | 递归的终止条件为$b=0$,这时候$x=1,\ y=0,\ a = gcd$。 172 | 173 | \lstinputlisting[language=C++, style=codestyle2]{code01/exgcd.cpp} 174 | 175 | \vbox{} 176 | 177 | \vbox{} 178 | 179 | \begin{problemset} 180 | \item 拓展欧几里得算法的时间复杂度是多少? 181 | \item \href{http://poj.org/problem?id=1061}{青蛙的约会 \quad POJ1061} 182 | \item \href{https://ac.nowcoder.com/acm/contest/201/C}{Utawarerumono \quad ac.nowcoder.com/acm/contest/201/C} 183 | \item \href{https://codeforces.com/problemsets/acmsguru/problem/99999/140}{SGU140 \quad 多元线性同余方程} 184 | \end{problemset} 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /02prime.tex: -------------------------------------------------------------------------------- 1 | \chapter{素数} 2 | \begin{introduction}[本章内容提要] 3 | \item 质因数分解 4 | \item 筛选素数 5 | \item 大区间问题 6 | \item 梅森素数 7 | \item 完全数 8 | \item 因数之和函数 9 | \end{introduction} 10 | 11 | \section{质因数分解} 12 | \begin{definition}{素数}{prime} 13 | 素数$p$是大于1且它的因数只有1和$p$的整数。 14 | \end{definition} 15 | 16 | \begin{property} 17 | \label{pro:prime} 18 | 令$p$是素数,假设$p$整除乘积$ab$,则$p$整除$a$或$p$整除$b$(或者$p$既整除$a$也整除$b$)。 19 | \end{property} 20 | 21 | \begin{theorem}{无穷多素数定理}{label} 22 | 存在无穷多个素数。 23 | \end{theorem} 24 | 25 | \begin{proof} 26 | 证明的思路是,假设我们现在有一个素数表,只要说明如何利用这些素数找出新的不在表中的素数,这样迭代下去,就 27 | 表明必有无穷多个素数。设素数表一开始为$p_1,p_2,...,p_n$,令$A = p_1p_2...p_n + 1$,设$q$是某个整除$A$的素数,且设其是 28 | 最小的那个,那么$q$一定不在最初的素数表中,因为如果$q=p_i$,则$q$能整除1,显然错误。于是我们可以无限扩充素数表。 29 | \end{proof} 30 | 31 | \begin{theorem}{素数定理}{label} 32 | 当x很大时,小于x的素数的个数近似等于$x/ln(x)$,即 33 | $$ 34 | \lim_{x\to\infty} \frac{\pi(x)}{x/ln(x)}=1 35 | $$ 36 | 其中$\pi(x)$为素数计数函数,$\pi(x) = \#\{prime\ p:p\le x\}$。 37 | \end{theorem} 38 | 39 | \begin{theorem}{素数整除性质}{label} 40 | 假设素数$p$整除乘积$a_1a_2...a_r$ ,则$p$整除$a_1 \quad ,a_2 \quad , ...,\quad a_r$ 中至少一个因数。 41 | \end{theorem} 42 | 43 | \begin{proof} 44 | 使用上面的\hyperref[pro:prime]{性质}可以方便的证明该定理。 45 | \end{proof} 46 | 47 | \vbox{} 48 | 49 | 下面将用{\heiti 素数整除性质}证明每个正整数可唯一分解成素数的乘积(算术基本定理)。 50 | 51 | \begin{theorem}{算术基本定理}{suanshu} 52 | 每个整数$n>=2$可唯一分解成素数乘积$n=p_1p_2...p_r$。 53 | \end{theorem} 54 | 55 | \begin{proof} 56 | 算术基本定理包含两个断言: 57 | 58 | 断言1:数$n$可以以某种方式分解成素数的乘积; 59 | 60 | 断言2:仅有一种这样的因数分解方式(因数重排除外); 61 | 62 | - 对于断言1的证明用到归纳证明法 (当$N+1$是合数时,其必然可以分解成$N+1=n_1*n_2$,而前面已经归纳完毕,即$n_1,\ n_2$一定可以分解成素数,因此得证)。 63 | 64 | - 对于断言2,可以由定理\ref{thm:suanshu}证得。 65 | \end{proof} 66 | 67 | \vbox{} 68 | 69 | 下面考虑如何进行质因数分解。 70 | 71 | 简单直接的方法是试除$2,3,4,5,...$分解$n$,但效率较低。 72 | 考虑一个整数$n$,如果$n$本身不是素数,则必有整除$n$的素数$p\leqslant \sqrt{n}$ 。于是我们可以遍历$2\sim \sqrt{n}$ 73 | 进行试除,时间复杂度为$O(\sqrt{n})$。如果提前预处理出所有素数,则可每次只试除素数,时间复杂度为$O(\frac{\sqrt{n}}{ln \sqrt{n}})$。 74 | 75 | \lstinputlisting[language=C++, style=codestyle2]{code02/factor.cpp} 76 | 77 | \section{区间素数筛} 78 | 79 | 这里区间素数筛指的是给定一个上限$n$,要求出区间$[1,n]$中所有的素数。如果采用直接遍历每个数并判断是不是素数的方法,时间复杂度为$O(n^{3\over2})$,若$n>10^6$,则 80 | 不能在$1s$的时间内求解。下面介绍的两种筛法,均使用“筛选”的思想,时间复杂度分别为$O(nlog(logn))$和$O(n)$,在比赛中经常使用。 81 | 82 | \subsection{埃氏筛法} 83 | 考虑如果一个数$n$如果是合数(非素数),那么它一定有小于等于$\sqrt{n}$的质因子,也即$n$是这个质因子的倍数。那么我们可以从小到大($2\sim n$)依次遍历每个数,并存在一些标记记录每个数是否是合数,当遍历到某个数 84 | 时,如果当前数没有被标记为合数(就一定是素数了),就把当前数的所有倍数标记为合数。代码如下: 85 | 86 | \lstinputlisting[language=C++, style=codestyle2]{code02/aishi.cpp} 87 | 88 | \begin{remark} 89 | 代码中有两个关键的地方: 90 | \begin{itemize} 91 | \item 第10行,只有当是素数时,才进行筛选,因为使用合数筛是多余的(该合数的质因子已经筛过了); 92 | \item 第12行,从$i*i$开始筛,因为$(i-1)*i,\ (i-2)*i,\ ...\ 2*i$已经被筛过了。 93 | \end{itemize} 94 | \end{remark} 95 | 96 | 考虑埃氏筛法的时间复杂度,由于每次只考虑素数去筛,即为: 97 | $$ 98 | n\sum_{p\le n} \frac{1}{p}\ , \quad p \ is \ prime 99 | $$ 100 | 上式可近似认为是$O(nloglogn)$,感兴趣的可以阅读\href{http://www.wikiwand.com/en/Sieve_of_Eratosthenes#/Algorithm_complexity}{Sieve\_of\_Eratosthenes\#/Algorithm\_complexity} 101 | 102 | \vbox{} 103 | 104 | 观察埃氏筛法,是否有重复被筛去的合数呢?有的,就是一些质数的较大的公倍数,比如12,当i=2时(从4开始)会筛12;当i=3时(从9开始)还会筛12。 105 | 106 | \subsection{欧拉线性筛法} 107 | 108 | 欧拉筛的思想是{\heiti 保证每个合数都被其最小的质因子筛去},这样就不会像埃氏筛那样重复筛某个数。 109 | 110 | 首先其还是筛选法,考虑用什么筛的:对于每个i,乘以小于i的每个素数,直到能整除就停止,这样能保证每个数只被筛去一次。 111 | 112 | 这个整除停止很关键,假设没有这个break,就会有很多重复。 113 | 114 | 比如$i=6$,取质数3,筛去了18;$i=9$,取质数2,也筛去了18。 115 | 116 | 再比如$i=6$,取质数5,筛去了30;$i=10$,取质数3,筛去了30;$i=15$,取质数2,筛去了30。 117 | 118 | 如果加上break,则筛去18的是9*2,筛去30的是15*2。为什么18不会被6*3筛去? 因为$6\%2=0$ 119 | 提前break了。即若有一个素数$p|i$,则我用i和后面更大的素数去筛选,不如用p和更大的i去筛选, 120 | 这样保证了筛掉每个数的是其最小质因子。 121 | 122 | 123 | \lstinputlisting[language=C++, style=codestyle2]{code02/euler.cpp} 124 | 125 | 由于每个数只会被筛去一次,时间复杂度是$O(n)$。 126 | 127 | \subsection{区间数的最大、最小质因子} 128 | 上面介绍的埃氏筛法和欧拉筛法经过一点改动就可以用来求区间数的最大质因子和最小质因子。 129 | 130 | 对于区间最大质因子,稍微增加点埃氏筛的复杂度,从$2*i$而不是$i*i$开始筛: 131 | 132 | \lstinputlisting[language=C++, style=codestyle2]{code02/maxprime.cpp} 133 | 134 | 对于区间最小质因子,只需修改欧拉筛中标记数组即可,时间复杂度不变: 135 | 136 | \lstinputlisting[language=C++, style=codestyle2]{code02/minprime.cpp} 137 | 138 | 139 | \section{区间质因数分解} 140 | 这里区间质因数分解指的是给定一个上限$n$,要求出区间$[1,n]$中所有数质因数分解的结果。 141 | 可以用埃氏法先求出所有数的所有质因子;或者使用欧拉法求出区间数的最小质因子再利用区间信息求解。 142 | \subsection{埃氏法} 143 | 先$O(nlogn)$求出每个数的所有质因子,再$O(nlogn)$直接除求出质因子对应的指数: 144 | 145 | \lstinputlisting[language=C++, style=codestyle2]{code02/interaishi.cpp} 146 | 147 | \subsection{欧拉法} 148 | 先$O(n)$欧拉筛求出每个数的{\heiti 最小质因子},再$O(nlogn)$利用区间信息求出对应的指数。 149 | 区间信息指的是,对于每个数如果我们都知道其最小质因子,则可以先将当前数的最小质因子除完,剩下的数的最小质因子我们还是知道的,于是继续除,直到1为止。 150 | \lstinputlisting[language=C++, style=codestyle2]{code02/intereuler.cpp} 151 | 152 | \section{大区间素数筛与质因数分解} 153 | 大区间指的是咱们考虑的区间不再是$[1,n]$,而是$[L,R]$,这里$R\le 2^{40}, \ R-L<=10^6$。 154 | 155 | \subsection{大区间素数筛} 156 | 对于一个合数$n$,一定有小于等于$\sqrt{n}$的质因子,利用这一点,我们依然可以使用埃氏筛的思想。 157 | 先预处理出$[1,\sqrt{R}]$区间内的所有素数,然后拿这些素数去做筛选即可。 158 | 159 | 时间复杂度为$O(N + llogl)$ , 其中$N$为$\sqrt{R}$,$R$是区间右端点,即需要预处理的素数范围 ; $l$为区间长度$R-L$。 160 | 161 | \lstinputlisting[language=C++, style=codestyle2]{code02/large-inter-prime.cpp} 162 | 163 | \subsection{大区间质因数分解} 164 | 和上面类似,先埃筛求出小于等于$\sqrt{R}$的所有质因子,然后对于区间内的每个数直接除即可。 165 | 166 | 因为一个合数n大于$\sqrt{n}$ 的质因子最多只有1个,因此将已知的质因子全部除尽后,若不为1,就为那个大于$\sqrt{n}$的质因子,对应指数为1。 167 | 168 | 时间复杂度为$O(N + llogl)$,其中$N$为$\sqrt{R}$,$R$是区间右端点,即需要预处理的素数范围 ; $l$为区间长度$R-L$。 169 | 170 | \lstinputlisting[language=C++, style=codestyle2]{code02/large-inter-factor.cpp} 171 | 172 | 173 | \section{梅森素数与完全数} 174 | 梅森素数与完全数在程序设计竞赛中不是很常见,这里简要介绍一下。 175 | 176 | \subsection{梅森素数} 177 | 现在我们来研究形如$a^n-1(n\ge2)$的素数。例如31就是这样的数,因为$31 = 2^5 - 1$。 178 | 由于$a-1$始终是$a^n-1$的因子。为啥呢?因为使用几何级求和公式:(展开即可证明该公式) 179 | $$ 180 | x^n-1=(x-1)(x^{n-1}+x^{n-2}+...+x^2+x+1) 181 | $$ 182 | 所以若$a^n-1$为素数,则a一定等于2。反之显然不成立。 183 | 184 | 再进一步考虑$n$,假设$n$为合数,即$n=mk$,则$2^n=2^{mk}=(2^m)^k$。使用$x=2^m$的几何级数公式得: 185 | $$ 186 | 2^n - 1 = (2^m)^k - 1 = (2^m-1)((2^m)^{k-1}+(2^m)^{k-2}+...+(2^m)^2+(2^m)+1) 187 | $$ 188 | 这证明了若$n$是合数,则$2^n-1$也是合数。综上有以下命题: 189 | 190 | \begin{proposition}{}{label} 191 | 如果对整数$a\ge2$与$n\ge2$,$a^n-1$是素数,则$a$必等于2且$n$一定是素数。 192 | \end{proposition} 193 | 194 | 形如$2^p-1$的素数叫做{\heiti 梅森素数},已知的梅森素数在\href{https://www.mersenne.org/primes/}{https://www.mersenne.org/primes/}中可以找到, 195 | 目前已经找到了51个(截至2019.09.03)。发现更多的梅森素数没有太大的数学意义,数学上更令人感兴趣的是下面的问题,其答案尚未知晓: 196 | 197 | \begin{custom}{问题} 198 | 存在无穷多个梅森素数吗? 199 | \end{custom} 200 | 201 | \subsection{完全数} 202 | 203 | \begin{definition}{完全数}{wanquanshu} 204 | 完全数是等于其真因数之和的数。真因数指的是小于自己的因数(即除去自己)。 205 | \end{definition} 206 | 207 | 208 | \begin{theorem}{欧几里得完全数公式}{label} 209 | 如果$2^p-1$是素数,则$2^{p-1}(2^p-1)$是完全数。 210 | \end{theorem} 211 | 212 | \begin{proof} 213 | 将$2^{p-1}(2^p-1)$所有真因数直接写出求和即证。 214 | \end{proof} 215 | 216 | 这里的$(2^p-1)$不就是梅森素数吗?也就是说只要求得一个梅森素数,就得到一个完全数。 217 | 218 | 那欧几里得完全数公式是否表示了所有完全数呢?千年之后的欧拉证明了欧几里得完全数公式至少给出了所有{\heiti 偶完全数},称为欧拉完全数定理。 219 | 220 | 在介绍和证明欧拉完全数定理之前,我们先来介绍一个函数$\sigma(n)$。 221 | 222 | \begin{center} 223 | $\sigma(n) = n$的所有因数之和(包括1和n)。 224 | \end{center} 225 | 226 | 例如$\sigma(6) = 1 + 2+3+6 = 12$。下面试着写出其一般公式,$\sigma(p) = p+1$,$\sigma(p^k) = 1 + p+p^2+...+p^k = \frac{p^{k+1}-1}{p-1}$。 227 | 228 | 如果你用程序打个表,也许会发现$\sigma(mn)$时常会等于$\sigma(m)\sigma(n)$,实际上这在$m$和$n$互质时成立。 229 | 230 | \begin{theorem}{$\sigma$函数公式}{label} 231 | \begin{itemize} 232 | \item 如果$p$是素数,$k\ge1$,则$\sigma(p^k) = 1 + p+p^2+...+p^k = \frac{p^{k+1}-1}{p-1}$; 233 | \item 如果$gcd(m,n)=1$,则$\sigma(mn) = \sigma(m)\sigma(n)$。 234 | \end{itemize} 235 | \end{theorem} 236 | 237 | $\sigma$函数在程序设计竞赛中也时常出现(或者其类似函数,如因子个数函数),后面章节还会说到它,现在我们用它来辅助证明欧拉完全数定理。 238 | 239 | 显然,$\sigma$函数可以和完全数产生联系,如果$\sigma(n)=2n$,则n恰好是完全数。 240 | 241 | \begin{theorem}{欧拉完全数定理}{label} 242 | 如果n是偶完全数,则$n$一定是$2^{p-1}(2^p-1)$ 的形式,其中$2^p-1$是梅森素数。 243 | \end{theorem} 244 | 245 | \begin{proof} 246 | 假设n是偶完全数,n是偶数则说明可将它分解成$n=2^km,\quad k\ge 1 \ \&\ m\ is\ odd$ 247 | 248 | 则$\sigma(n)=\sigma(2^km)=\sigma(2^k)\sigma(m)=(2^{k+1}-1)\sigma(m)$。 249 | 又n是完全数,则$\sigma(n)=2n=2^{k+1}m$。 250 | 251 | 因此可以得到 252 | $$ 253 | 2^{k+1}m=(2^{k+1}-1)\sigma(m) 254 | $$ 255 | 由上式可知,由于$(2^{k+1}-1)$一定是奇数,所以$2^{k+1}| \sigma(m)$ ,于是设$\sigma(m)=c*2^{k+1}$ ,带入上式有: 256 | \begin{align*} 257 | 2^{k+1}m=(2^{k+1}-1)*c*2^{k+1} \\ 258 | m=(2^{k+1}-1)*c 259 | \end{align*} 260 | 现在我们有两个式子,$m=(2^{k+1}-1)*c$ 和$\sigma(m)=c*2^{k+1}$。 261 | 262 | 实际上这里c只能等于1,下面我们来证明$c=1$。 263 | 264 | 假设$c>1$,则$m=(2^{k+1}-1)*c$至少被不同的数$1,c,m$所整除,因此有 265 | $$ 266 | \sigma(m)\ge 1+c+m=1+c+(2^{k+1}-1)*c 267 | $$ 268 | 显然,这是错误的(会得出$0\ge 1$),因此$c=1$,所以$m=2^{k+1}-1$ , $\sigma(m)=2^{k+1}=m+1$,所以m是素数。 269 | 270 | 现在我们已经证明了,如果n是偶完全数,则 271 | $$ 272 | n=2^k(2^{k+1}-1)\ ,\quad where \ (2^{k+1}-1)\ is \ prime 273 | $$ 274 | 由于$2^{k+1}-1$是素数,则$(k+1)$一定是素数,(上节已说明)。令$k+1=p$,则$n=2^{p-1}(2^p-1)$,其中$2^p-1$是梅森素数。 275 | 276 | 欧拉完全数定理证毕。 277 | \end{proof} 278 | 279 | \vbox{} 280 | 281 | \begin{note} 282 | 关于完全数的几个结论: 283 | \begin{itemize} 284 | \item 目前还没找到奇完全数 285 | \item 每个完全数的全部因数倒数之和都是2 286 | \item 除了6以外的完全数,每个都可以表示成连续奇数的立方和 287 | \item 每个完全数都可以表示成2的一些连续正整数次幂之和 288 | \item 完全数都是以6、8结尾 289 | \end{itemize} 290 | \end{note} 291 | 292 | \vbox{} 293 | 294 | \begin{custom}{问题} 295 | 存在奇完全数吗? 296 | \end{custom} 297 | 298 | \vbox{} 299 | 300 | \vbox{} 301 | 302 | 303 | \begin{problemset} 304 | \item 区间数的最大质因子是否有线性时间的算法? 305 | \item 大区间数的最小质因子是否有线性时间的算法? 306 | \item 试着写出求单点$\sigma(n)$以及区间$[1,n]$所有$\sigma$值的代码。 307 | \end{problemset} -------------------------------------------------------------------------------- /03congruence.tex: -------------------------------------------------------------------------------- 1 | \chapter{同余} 2 | 3 | \begin{introduction} 4 | \item 同余方程 5 | \item 快速乘、快速幂 6 | \item 逆元、线性求区间逆元 7 | \item 中国剩余定理 8 | \item 欧拉降幂 9 | \item 卡米歇尔数 10 | \item Miller\_Rabin 11 | \item Pollard\_Rho 12 | \item 离散对数 13 | \item 原根 14 | \end{introduction} 15 | 16 | 17 | \section{同余式与同余方程} 18 | \subsection{同余式} 19 | 整除性是很好的性质,这在最大公因数、模线性方程和素数分解中均得到了体现。而同余式提供了一种 20 | 描述整除性质的简便方式。 21 | 22 | \begin{definition}{同余}{label} 23 | 如果$m|(a-b)$,我们就说a与b模m同余并记之为$a\equiv b(mod\ m)$。 24 | 25 | 特别地,$a\equiv a\%m(mod\ m)$ 。 26 | \end{definition} 27 | 28 | 数m叫做同余式的模。 具有相同模的同余式在许多方面表现得很像通常的等式。例如: 29 | 30 | 若$a_1\equiv b_1(mod\ m) \ ,\quad a_2\equiv b_2(mod\ m)$ 则$a_1\pm a_2\equiv b_1\pm b_2(mod \ m)$ ,$a_1a_2\equiv b_1b_2(mod \ m)$ 31 | 32 | {\heiti 但 $ac\equiv bc(mod \ m)$ 时,未必有$a\equiv b(mod \ m)$,只有$gcd(c,m)=1$,才可以消去c。} 33 | 34 | 35 | \subsection{同余方程} 36 | 如果同余式含有未知数,我们考虑如何求解。 37 | 38 | 首先考虑“穷举法” ,要解模m同余式,可让每个变量试取0,1,2...,m-1。例如,解同余式$x^2+2x-1\equiv 0(mod\ 7)$, 39 | 就去试$x=0\quad, x=1\quad,...\quad,x=6$,这样可以求出两个解$x\equiv 2(mod\ 7)$和$x\equiv 3(mod\ 7)$ 当然还有其他解,但新的解与2或3是同余的,如9和10。当我们说“求同余式的所有解时”,是指求所有不同余的解,即相互不同余的所有解。 40 | 41 | 许多同余式是没有解的,如$x^2\equiv 3(mod\ 10)$。 42 | 43 | \vbox{} 44 | 45 | 下面考虑如何求解同余式$ax\equiv c(mod\ m)$。 46 | \begin{example} 47 | 解同余式$18x\equiv8(mod\ 22)$ 48 | \end{example} 49 | 50 | 等价于求$22\ |\ (18x-8)$,即求$18x-22y=8$。 51 | 52 | 解这类方程的问题在第一章中研究过。 53 | 54 | 对于$ax\equiv c(mod\ m)$ ,其有解{\heiti 当且仅当}线性方程$ax-my=c$ 有解。 55 | 56 | 57 | 由前可知,线性方程$ax-my=c$ 有解的充分必要条件是$gcd(a,m)\ |\ c$。 58 | 59 | 且方程$au+mv=g$ 一定有解 ,设一个解为$(u_0,v_0)$,则有 60 | $$ 61 | a\frac{cu_0}{g}+m\frac{cv_0}{g}=c 62 | $$ 63 | 这说明$x_0= \frac{cu_0}{g}$是同余式$ax\equiv c(mod\ m)$ 的一个解,通解为$x=x_0+k\cdot \frac{m}{g}$。 64 | 65 | {\heiti 由于相差m的倍数的任何两个解认为是相同的},所有恰好有$g$个不同的解,这些解通过取$k=0,1,2...,g-1$而得到。将上述过程概述为定理: 66 | 67 | 68 | \begin{theorem}{线性同余式定理$ax\equiv c(mod\ m)$}{label} 69 | 设a,c与m是整数,m>=1,且设$g=gcd(a,m)$。 70 | 71 | (a) 如果$g\nmid c$,则同余式$ax\equiv c(mod\ m)$ 没有解 72 | 73 | (b) 如果$g\ |\ c$ ,则同余式$ax\equiv c(mod\ m)$ 恰好有g个不同的解。要求这些解,首先求线性方程$au+mv=g$ 的一个解$(u_0,v_0)$ 74 | {\heiti (欧几里得回带法,在计算机上递归求得,称为扩展欧几里得算法)} 。则$x_0=(\frac{cu_0}{g}\% \frac{m}{g} + \frac{m}{g}) \% \frac{m}{g}$是$ax\equiv c(mod\ m)$ 的解,不同余解的完全集由 75 | $$ 76 | x\equiv x_0+k\cdot \frac{m}{g} \ (mod\ m),\quad k=0,1,2,...,g-1 77 | $$ 78 | 给出。 79 | \end{theorem} 80 | 81 | 82 | 例如,同余式$943x\equiv 381(mod\ 2576)$ 无解,这是因为$gcd(943,2576) \nmid 381$。 83 | 84 | 另一方面,同余式$893x\equiv 266(mod\ 2432) $ 85 | 有19个解,因为$gcd(893,2432)=19,\ 19|266$ 这个19即为不同余解的个数。 86 | 87 | 下面解方程$893u-2432v=19$ ,使用欧几里得回带法可以求得解$(u,v)=(79,29)$ ,乘以266/19=14得方程$893x-2432y=266$的解$(x,y)=(1106,406)$ , 88 | 即1106是同余式方程的一个解,这样的互不同余的解共有19个。1106加上2432/19=128的倍数(\%2432)就可得到完全解集。 89 | 90 | 91 | \begin{remark} 92 | 线性同余式定理最重要的情形是$gcd(a,m)=1$ ,在这种情形下,同余式恰好有一个解。 93 | \end{remark} 94 | 95 | \lstinputlisting[language=C++, style=codestyle2]{code03/modequation.cpp} 96 | 97 | \vbox{} 98 | 99 | 对于非线性的同余式,其解“不是很确定”。 100 | 101 | 我们熟悉的是对于{\heiti 一个d次实系数多项式的实根不超过d个} ,这个结论对于同余式并不成立。 102 | 103 | 例如$x^2+x\equiv 0(mod\ 6)$ 有4个模6不同的根:0,2,3,5。 104 | 105 | 但是,{\heiti 当p为素数时,这个结论依然成立:} 106 | 107 | \begin{theorem}{模p多项式根定理}{label} 108 | 设p为素数,$f(x)=a_0x^d+a_1x^{d-1}+...+a_d$ 是次数为d>=1的整系数多项式,且p不整除$a_0$ ,则同余式$f(x)\equiv 0(mod\ p)$ 最多有d个模p不同余的解。 109 | \end{theorem} 110 | 111 | \section{快速乘与快速幂} 112 | 快速乘和快速幂作为工具经常在程序设计竞赛中遇见。 113 | \subsection{快速乘} 114 | 在$C++$中,变量最多只能表示到$2^{64}-1$这么大,所以如果我们要计算$a*b\%c$,而$a,b$都是接近表示上限的数,这个时候就需要快速乘。 115 | 即将$b$按二进制位分解分别加上,时间复杂度为$O(log(b))$。 116 | \lstinputlisting[language=C++, style=codestyle2]{code03/fastmul.cpp} 117 | 118 | \subsection{$O(1)$快速乘} 119 | 上面的“快速乘”时间是$O(logb)$的,如果是两个63位数相乘(结果long long 存不下),则还有一个巧妙的方法可以简单解决这个问题: 120 | 121 | \lstinputlisting[language=C++, style=codestyle2]{code03/o1fastmul.cpp} 122 | 123 | 首先使用浮点运算来得到$\left\lfloor\frac{a * b}{P}\right\rfloor$的值,显然$a*b-d*p$中的两个乘法都有可能会溢出,但是没关系,因为可以知道其差是一个64bit可容纳的正整数,那么溢出部分的差仅可能为0或者1,最后 124 | 特判处理一下即可。 125 | \begin{note} 126 | 当然,如果编译器支持$int128$的话,可以直接用128位数。 127 | \end{note} 128 | 129 | \subsection{快速幂} 130 | 现在我们要计算$a^b\%c$,如果乘$b$次,时间复杂度太高,考虑将$b$按照二进制分解,每一位分别计算并乘在一起即可。 131 | 时间复杂度为$O(log(b))$。相比于快速乘,只是加法变了乘法。 132 | \lstinputlisting[language=C++, style=codestyle2]{code03/fastexp.cpp} 133 | 134 | \begin{note} 135 | 快速幂可以使用不同的进制,以及在特定问题下做一些预处理进行记忆化,可以节省时间。 136 | 137 | 如\href{https://nanti.jisuanke.com/t/41355}{2019icpc南昌网络赛\quad The Nth Item}。 138 | \end{note} 139 | 140 | 141 | \section{费马小定理与逆元} 142 | 前面我们讨论了关于同余式、同余方程的一些性质,小结一下,互质这个条件很重要。 143 | 144 | \begin{itemize} 145 | \item 对于$ac\equiv bc(mod \ m)$,如果$gcd(c,m)=1$,则可以消去c,得到$a\equiv b(mod \ m)$ 。 146 | \item 对于同余方程 $ax\equiv c(mod\ m)$,若$gcd(a,m)=1$ ,同余式恰好有一个解。 147 | \end{itemize} 148 | 149 | {\heiti 对于式子$ax\equiv c(mod\ m)$,令$c=1$,得$ax\equiv 1(mod\ m)$,若$gcd(a,m)=1$,则方程有唯一解$x_0$, 150 | 我们称$x_0$为$a$在模$m$意义下的逆元,常记作$a^{-1}$。} 151 | 152 | 逆元可以用来说明一些事情,比如如果$ac\equiv bc(mod\ m)$,若$gcd(c,m)=1$, 153 | 则存在$c^{-1}$使得$cc^{-1}\equiv 1 (mod\ m)$。所以可以对$ac\equiv bc(mod\ m)$两边同时乘以$c^{-1}$,得到$a\equiv b(mod\ m)$。 154 | 155 | 如何求解逆元呢?拓展欧几里得即可,因为就是一个同余方程: 156 | \lstinputlisting[language=C++, style=codestyle2]{code03/inverse.cpp} 157 | 158 | \vbox{} 159 | 160 | 若模数是素数,我们还可以用费马小定理求解。 161 | 162 | \begin{theorem}{费马小定理}{femat} 163 | 设p是素数,a是任意整数且$p\nmid a$ ,则 $a^{p-1}\equiv 1(mod\ p)$。 164 | \end{theorem} 165 | 166 | 在证明费马小定理之前,先来看一个引理。 167 | 168 | \begin{lemma}{为证明费马小定理做准备}{forfemat} 169 | 设$p$是素数,a是任何整数且 $p\nmid a$ 则数 170 | $$ 171 | a,2a,3a,...,(p-1)a\qquad (modp) 172 | $$ 173 | 与数 174 | $$ 175 | 1,2,3,...,(p-1)\qquad (modp) 176 | $$ 177 | 相同,尽管它们的次序不同。 178 | \end{lemma} 179 | 180 | \begin{proof} 181 | 数列$a,2a,3a,...,(p-1)a$,包含$p-1$个数,显然没有一个数被$p$整除 ,假设从中取出两个数ja和ka是关于p同余的,即$p|(j-k)a$, 182 | 又p是素数且p不整除a,所以p整除$(j-k)$。但是$|j-k|\phi(p) 396 | \end{matrix}\right. \quad {mod \ p} 397 | \end{align*} 398 | \end{theorem} 399 | 400 | 401 | \begin{proof} 402 | 第一行和第二行的式子之前已经说明,下面证明$b>\phi(p)$的情况。 403 | 404 | 设$b = A*\phi(p) + C$,其中$A\ge1,\ 0\le C<\phi(p)$。 405 | 406 | 那么我们要证明的就是$a^{ A*\phi(p) + C} \equiv a^{\phi(p)+C}(\ mod\ p)$。 407 | 408 | 如果我们能证明$a^{A*\phi(p)} \equiv a^{\phi(p)}( mod\ p)$,则上式也就成立。 409 | 410 | 即证$a^{2*\phi(p)} \equiv a^{\phi(p)}( mod\ p)$,移项即证 411 | 412 | $$p\ |\ a^{\phi(p)}(a^{\phi(p)}-1)$$ 413 | 414 | (这里p不一定是素数) 415 | 416 | 假设 417 | $$ 418 | (\frac{p}{(p\ ,\ a^{\phi(p)})}\ ,\ a)= 1 419 | $$ 420 | 421 | 那么根据欧拉定理, 422 | $$ 423 | a^{\phi(p)} = a^{k*\phi(\frac{p}{ {(p,a^{\phi(p)})}})}\equiv {[a^{\phi(\frac{p}{ {(p,a^{\phi(p)})}})}]}^k \equiv 1 \ ,\quad mod\ (\frac{p}{(p,a^{\phi(p)})}) 424 | $$ 425 | 其中$k\ge 1$,移项可得$\frac{p}{(p,a^{\phi(p)})}\ |\ (a^{\phi(p) }- 1)$。 426 | 两边同时乘${(p,a^{\phi(p)})}$可得$p\ |\ {(p,a^{\phi(p)})}*(a^{\phi(p)}-1)$,于是也就证明了$p\ |\ a^{\phi(p)}(a^{\phi(p)}-1)$。证毕。 427 | 428 | 但上面的假设还没有证明,实际上这个假设是一定成立的,下面证明。 429 | 430 | 对$a$和$p$进行质因数分解, 431 | $$ 432 | a = p^{a_1}_1*p^{a_2}_2*....*p^{a_{t_1}}_{t_1} * q^{b_1}_1* q^{b_2}_2*...* q^{b_{t_2}}_{t_2} 433 | $$ 434 | 435 | $$ 436 | p = p^{c_1}_1*p^{c_2}_2*....*p^{c_{t_1}}_{t_1} * r^{d_1}_1* r^{d_2}_2*...* r^{d_{t_3}}_{t_3} 437 | $$ 438 | 则$(a,p) = p^{min(a_1,c_1)}_1*p^{min(a_2,c_2)}_2*....*p^{{min(a_{t_1},c_{t_1})}}_{t_1}$, 439 | 440 | $(a^{\phi(p)},p) = p^{min(a_1*\phi(p),c_1)}_1*p^{min(a_2*\phi(p),c_2)}_2*....*p^{{min(a_{t_1}*\phi(p),c_{t_1})}}_{t_1}$。 441 | 442 | 我们分析一下$a_i*\phi(p)$,$a_i*\phi(p)\ge a_i*p_i^{c_i-1}*(p_i-1) \ge p_i^{c_i-1}*(p_i-1) \ge p_i^{c_i-1}\ge c_i$。(其中$p_i$是$p$的因子)。 443 | 444 | 于是有$(a^{\phi(p)},p) = p^{min(a_1*\phi(p),c_1)}_1*p^{min(a_2*\phi(p),c_2)}_2*....*p^{{min(a_{t_1}*\phi(p),c_{t_1})}}_{t_1} = p^{c_1}_1*p^{c_2}_2*....*p^{c_{t_1}}_{t_1}$。 445 | 446 | 于是 447 | $$ 448 | (\frac{p}{(p\ ,\ a^{\phi(p)})}\ ,\ a)= 1 449 | $$ 450 | 证毕。 451 | \end{proof} 452 | 453 | 实际上,广义欧拉降幂公式说明的是$a^b\%c$循环节的问题, 454 | 455 | 这里\href{https://math.stackexchange.com/questions/653682}{https://math.stackexchange.com/questions/653682} 456 | 有相关的讨论,$\phi(c)$不一定是最小的循环节长度。 457 | 458 | \begin{example} 459 | 2019南京网络赛B.superlog,题目经简化后即求$a^{a^{...}} \% m$的值,其中$...$共有$b$个$a$,数据范围是$a,b,m\ \le 10^6$。 460 | \end{example} 461 | 462 | 由于$m$的范围已知,发现只有很少个形如$a^{a^{...}}$的式子的真值会比$m$小,提前预处理一下即可,这也是比较常见的做法,因为指数套指数增长的非常快。 463 | 464 | 代码如下,时间复杂度为$O(T*log(m)*\sqrt{m})$。 465 | 466 | \lstinputlisting[language=C++, style=codestyle2]{code03/superlog.cpp} 467 | 468 | 469 | \subsection{威尔逊定理} 470 | 在欧拉公式的证明中,我们记$B=b_1b_2b_3...b_{\phi(m)}$,即$B$为小于$m$且与$m$互质的数的乘积。那么我们能给出一个结论, 471 | $B\equiv 1(mod\ m)$或$B\equiv m-1(mod\ m)$。那$m$的模式是怎样的?(即何时为$1$,何时为$m-1$) 472 | 473 | \begin{theorem}{威尔逊定理}{wilson} 474 | 将$m$质因数分解,若其形如$2^2,p^k, 2*p^k$中的一种,则$B\equiv m-1(mod\ m)$,否则$B\equiv 1(mod\ m)$,其中$p$为奇素数。 475 | 476 | 特殊地,若$m$为素数,则$B=(m-1)!$,有$(m-1)! \equiv m-1 \ (mod\ m)$,反过来也成立。 477 | \end{theorem} 478 | 479 | 威尔逊定理给出了判定一个{\heiti 自然数是否为素数}的充分必要条件,但是由于阶乘是呈爆炸增长的,其结论对于实际操作意义不大。 480 | 实际(比赛)中,更常用的是一个叫$Miller\_Rabin$的测试,下面就来看一下如何做素性测试。 481 | 482 | \section{素性测试} 483 | 素数是整数中优美的一部分,怎么判别一个数是不是素数呢?费马小定理告诉我们$a^p\equiv a \ (\ mod \ p)$,首先,不满足上式的数,一定不是素数。 484 | 但就算你尝试了许多$a$之后发现都满足上式,也不能断言$p$就是素数! 485 | 486 | 确实,对于大部分小的合数$n$,你会发现选取的大部分$a(a10^9$,需要使用快速乘,认为是两个$log$)。 586 | \lstinputlisting[language=C++, style=codestyle2]{code03/miller.cpp} 587 | 588 | \section{Pollard\_Rho质因数分解} 589 | 590 | Pollard\_Rho算法是一种用于质因数分解的算法,对于一个待分解的数$N$,假设$N$的最小的质因数为$p(p\neq N)$,那么Pollard\_Rho算法 591 | 能够在$O(\sqrt{p}*\alpha(N))$的期望时间复杂度内将$N$分解为两个不是1的数的乘积,其中$\alpha(N)$是求解两个数的gcd的时间复杂度,而且 592 | 其不需要额外的空间!下面我们就来看一下它是怎么工作的。 593 | 594 | 我们之前质因数分解的方法就是去拿$1\sim \sqrt{N}$的所有数去试除,那如果$N$是$10^{18}$,就顶不住了。其实,可以随机去做这件事,如果运气好的话, 595 | 就可以将一个数分解为两个数相乘($\frac{1}{\sqrt{N}}$的概率)。有没有更好的方法,使得猜中其因数的概率增大呢? 596 | 597 | 我们先来考虑这样一个问题:在$[1,1000]$里随机选择一个数,是$23$的概率是多少?显然是$\frac{1}{1000}$,那如果我们选择两个数$i,j$,$|i-j|=23$的概率是多少? 598 | 答案大概是$\frac{1}{500}$。这给了我们一点启发,这种“多点采样”的方法,是否能提高选中目标的概率?著名的生日悖论就是这种思想! 599 | 600 | 假如说班上有$k$个人,如果找到一个人的生日是2月3日,这个概率比较低。但是如果单纯想找到两个生日相同的人,这个概率就会很高! 601 | 由高中的数学知识知道,$k$个人生日互不相同,其概率为:$p=\frac{365}{365}*\frac{364}{365}*\frac{363}{365}*...*\frac{365-k+1}{365}$, 602 | 故生日有重复的现象的概率是$P(k) = 1 - \Pi_{i=1}^k\frac{365-i+1}{365}$。当$P(k)\ge\frac{1}{2}$时,解得$k$大概只需要23。 603 | 当$k$取到$60$时,$P(k)\approx 0.9999$。这可能和我们的直觉不符,因此被称为悖论。 604 | 605 | 现在回到因数分解的问题上,由于一定有$gcd(k,n)\ |\ n$,如果我们通过一些组合选取适当的$k$,有$gcd(k,n)>1$,就找到了一个$n$的因数! 606 | 这样的$k$的数量还是蛮多的,假设$n$有若干个质因子,则每个质因子的倍数都是可行的(简单一想,至少得有$O(\sqrt{n})$个吧)。 607 | 于是,我们可以选取一组数$x_1,x_2,...,x_n$,若有$gcd(|x_i-x_j|,N)>1$,则$gcd(|x_i-x_j|,N)$是$N$的一个因子。 608 | 有前人在论文中指出,要使概率接近1,需要选取的数的个数大约是$O(N^{\frac{1}{4}})$。但是如果要$O(n^2)$枚举两两计算,还不如直接$\sqrt{N}$暴力。 609 | 610 | 我们不妨考虑构造一个伪随机数序列,然后取相邻的两项做差来求$gcd$,这样时间复杂度就降了下来。{\heiti 为了生成一串优秀的随机数},$Pollard$设计了这样一个函数: 611 | $f(x) = (x^2+c) \ mod\ N$,其中$c$是一个随机的常数。之所以叫伪随机数,是因为这个序列里可能会含有循环,比如取$c=7, N=20, x_1=1$,序列为$1,8,11,8,11,8,...$。 612 | 循环节的产生很自然,在模$N$的意义下,函数的值域为$0,1,2...,N-1$,只要有一次序列的值之前出现过,那么序列就会开始循环。如果画出这个序列,并让循环的地方指向之前,其轨迹 613 | 很像一个希腊字母$\rho$。(算法名字的由来) 614 | 615 | 为了方便地判断环的存在,可以用{\heiti 快慢指针}。 616 | 617 | 考虑有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部会形成环,如图\ref{fig:Fast-and-slow-pointer}。 618 | 现在有两个指针都指向开始节点$A$,然后向右移动,一个一次移动一个节点,另外一个一次移动两个节点,假设相遇在$P$点,那么可以证明一定有$P->B$的长度等于$A->B$。 619 | 对于上面的伪随机数序列,有$A->P$(不包含$P$)为一个完整的循环节。于是我们可以设置两个变量,一个变量每次向后迭代一次,而另一个每次向后迭代计算两次,在这两个 620 | 变量相等之前,取它们的差用于求$GCD$。 621 | 622 | \begin{figure}[htbp] 623 | \centering 624 | \includegraphics[width=0.7\textwidth]{Fast-and-slow-pointer.png} 625 | \caption{Fast-and-slow-pointer \label{fig:Fast-and-slow-pointer}} 626 | \end{figure} 627 | 628 | 代码如下: 629 | \lstinputlisting[language=C++, style=codestyle2]{code03/pollard-rho.cpp} 630 | 631 | 由于求$GCD$大概会有一个常数,考虑进一步优化。显然如果我们能求得$gcd(ab,N)>1$,那么也是找到了一个因子,只要$a,b$中某一个有$N$的因子即可。 632 | 所以我们可以先不急着做$GCD$,而是做一系列的$|t-s|$的连乘,到一定次数再做$GCD$。为了不溢出,我们使连乘的结果$mod\ N$,由欧几里得算法知答案不变。 633 | 634 | \lstinputlisting[language=C++, style=codestyle2]{code03/pollard-rho-with-multi.cpp} 635 | 636 | pollard-rho算法往往会和miller-rabin同时使用,比如章节题目\ref{prob:pollard}。 637 | 其要判断一个数是否是素数,若不是则输出其最大质因子。 638 | 639 | 代码如下,可作为模板,时间复杂度$O(T*\alpha*(N^{1\over 4}+log^2(N)))$。其中$T$表示数据组数,$\alpha$表示一个不小的常数。 640 | 641 | \lstinputlisting[language=C++, style=codestyle2]{code03/luogu-p4718.cpp} 642 | 643 | \section{离散对数} 644 | 645 | 现在我们来思考这样一个问题: 646 | 647 | \begin{custom}{问题} 648 | 给定$a,b,m$,求$a^x\equiv b \ (mod \ m)$的解$x$。 649 | \end{custom} 650 | 651 | \begin{solution} 652 | 653 | 我们设$x=A\left \lceil \sqrt{m} \right \rceil -B$,其中$0\le B< \left \lceil \sqrt{m} \right \rceil$, $0< A\le \left \lceil \sqrt{m} \right \rceil+1$,这样化简后的方程是 654 | $$ 655 | a^{A\left \lceil \sqrt{m} \right \rceil}\equiv b\cdot a^B \quad (mod \ m) 656 | $$ 657 | 由于$A$和$B$值域都是$O(\sqrt{m})$级别的,所以可以先计算右边部分的值,存入$Hash$表,然后从小到大枚举$A$计算左边的值,在$Hash$表中查找。 658 | (当然,可以这样做的原因是一定存在$a$的逆元)即只要$gcd(a,m)=1$,上面的方法就是有效的。所以当$m$是质数时,用这种方法(Baby Step Giant Step, bsgs)即可。 659 | 660 | {\heiti 当$m$不是质数时},我们要求解的是$a^x+km=b$,设$g=gcd(a,m)$,如果$g$不整除$b$,则无解,否则方程两边同除以$g$,得到$\frac{a}{g}a^{x-1}+k\frac{m}{g}=\frac{b}{g}$。 661 | 这样便消去了m的一个因子,得到方程 662 | $$ 663 | \frac{a}{g}a^{x-1}\equiv \frac{b}{g}\quad (mod \ \frac{m}{g}) 664 | $$ 665 | 令${m}'=\frac{m}{g} ,\ b'=\frac{b}{g}(\frac{a}{g})^{-1}$, 得到 666 | $$ 667 | a^{x'}\equiv b' \quad (mod \ m') 668 | $$ 669 | 于是可以递归,得到的解加1即$x=x'+1$为原方程的解。 670 | 671 | {\heiti 但是,进行一次这样的操作,新的方程不一定可以用bsgs求解,所以会进行多次。} 672 | 673 | 如果中途出现$b'=1$则$x'=0$。 674 | \end{solution} 675 | 676 | 时间复杂度 $O(\sqrt{m}log\sqrt{m})$,手写二分比unordered\_map会快一点。 677 | 678 | \lstinputlisting[language=C++, style=codestyle2]{code03/bsgs.cpp} 679 | 680 | 注意到代码中有一个技巧,不用把每一步的逆元实际求出来,放到式子左边乘起来就行。 681 | 查表时,把初值设置为这个数$*a^{\left \lceil \sqrt{m} \right \rceil}$即可。 682 | 683 | 684 | \section{原根} 685 | \subsection{幂模$p$与原根} 686 | 687 | 如果$a$和$p$互素,费马小定理告诉我们,$a^{p-1} \equiv 1\ (mod\ p)$,那么这个指数$p-1$是唯一的使得结果为1的吗? 688 | 我们选择一些$a$和$p$来看一下,对于$a=3,p=7$,指数只有为6时才取到1,如表\ref{tab:root}。 689 | 690 | 691 | \begin{table}[!htbp] 692 | \centering 693 | \caption{$a=3,\ p=7$ \label{tab:root}} 694 | \begin{tabular}{ccc} 695 | \midrule 696 | $3^1\equiv 3(mod\ 7)$ & $3^2\equiv 2(mod\ 7)$ & $3^3\equiv 6(mod\ 7)$ \\ 697 | $3^4\equiv 4(mod\ 7)$ & $3^5\equiv 5(mod\ 7)$ & $3^6\equiv 1(mod\ 7)$ \\ 698 | \bottomrule 699 | \end{tabular}% 700 | \end{table}% 701 | 702 | 再列多一点,如表\ref{tab:root2},从表中可以看出,似乎有这样的性质: 703 | \begin{itemize} 704 | \item 对于任何底数,最小指数$e$整除$p-1$; 705 | \item 总有一些底数,指数需要到$p-1$。 706 | \end{itemize} 707 | 708 | \begin{table}[!htbp] 709 | \centering 710 | \caption{不同底数对应的最小指数 \label{tab:root2}} 711 | \begin{tabular}{ccc} 712 | \toprule 713 | $p=5$ & $p=7$ & $p=11$ \\ 714 | \midrule 715 | $1^1\equiv 1(mod\ 5)$ & $1^1\equiv 1(mod\ 7)$ & $1^1\equiv 1(mod\ 11)$ \\ 716 | $2^4\equiv 1(mod\ 5)$ & $2^3\equiv 1(mod\ 7)$ & $2^{10}\equiv 1(mod\ 11)$ \\ 717 | $3^4\equiv 1(mod\ 5)$ & $3^6\equiv 1(mod\ 7)$ & $3^5\equiv 1(mod\ 11)$ \\ 718 | $4^2\equiv 1(mod\ 5)$ & $4^3\equiv 1(mod\ 7)$ & $4^5\equiv 1(mod\ 11)$ \\ 719 | & $5^6\equiv 1(mod\ 7)$ & $5^5\equiv 1(mod\ 11)$ \\ 720 | & $6^2\equiv 1(mod\ 7)$ & $6^{10}\equiv 1(mod\ 11)$ \\ 721 | & & $7^{10}\equiv 1(mod\ 11)$ \\ 722 | & & $8^{10}\equiv 1(mod\ 11)$ \\ 723 | & & $9^{5}\equiv 1(mod\ 11)$ \\ 724 | & & $10^{2}\equiv 1(mod\ 11)$ \\ 725 | \bottomrule 726 | \end{tabular}% 727 | \end{table}% 728 | 729 | 730 | 为了方便,我们定义$a$模$p$的阶指$e_p(a)=[min\ e\ s.t.\ a^e\equiv 1(mod \ p)]$,其中$a$和$p$互质。另外,规定$e_p(a)\ge 1$,显然$e_p(a)\le p-1$。 731 | 732 | \begin{theorem}{次数整除性质}{label} 733 | 设a是不被素数$p$整除的整数,假设$a^n \equiv 1 \ (mod \ p)$,则次数$e_p(a)$整除$n$,特别地$e_p(a)$总整除$p-1$。 734 | \end{theorem} 735 | 736 | \begin{proof} 737 | 次数$e_p(a)$的定义告诉我们 738 | $$ 739 | a^{e_p(a)}\equiv 1 \ (mod \ p) 740 | $$ 741 | 假设$a^n\equiv 1(\ mod \ p)$, 742 | 设$G=gcd(e_p(a),n)$, 743 | 并设$(u,v)$是方程$e_p(a)u-nv=G$的正整数解(可知一定有解)。现在有两种不同的方法计算$a^{e_p(a)u}$: 744 | \begin{align*} 745 | a^{e_p(a)u}=(a^{e_p(a)})^u\equiv 1 \ (mod \ p) \\ 746 | a^{e_p(a)u}=a^{nv+G}\equiv a^G \ (mod \ p) \\ 747 | \end{align*} 748 | 749 | 这表明$a^G\equiv 1 (\ mod \ p)$ ,所以必有$e_p(a)\le G$。 750 | 751 | 另一方面$G \ | \ e_p(a)$,所以$G=e_p(a)\ ,\ e_p(a)\ | \ n$,证毕。 752 | \end{proof} 753 | 754 | {\heiti 现在我们的一个猜想得到了证明,来看另外一个:$e_p(a)=p-1$的底数$a$有什么规律。} 755 | 756 | 如果a是这样的数,则幂 757 | $$ 758 | a^1,\ a^2,\ a^3,\ ...\ ,\ a^{p-2},\ a^{p-1}\quad (\ mod \ p) 759 | $$ 760 | {\heiti 必须都是模$p$不同的。}[如果幂不是全不相同,则对某指数$1\le i 2)$互素的任意两个数$a,b$,由指标的乘积法则知$I(ab)\equiv I(a)+I(b) \quad (mod \ p-1)$, 112 | 从而有$I(ab)\equiv I(a)+I(b) \quad (mod \ 2)$。 后面的证明就很自然了。可以讨论定理\ref{thm:residue1}中的三种情况。 113 | \end{proof} 114 | 115 | \vbox{} 116 | 117 | 对于定理\ref{thm:residue1},你肯定会想到$QR,NR$和$+1,-1$的性质类似。 118 | 许多年前,勒让德(Adrien-Marie Legendre)也想到了,而且还引入了一种符号: 119 | 120 | 121 | \begin{definition}{勒让德符号}{label} 122 | $a$模$p$的勒让德符号是 123 | \begin{align*} 124 | \left(\frac{a}{p}\right)=\left\{\begin{matrix} 125 | 1 & \quad if\ a\ is\ Quadratic\ residue \\ 126 | -1& \quad else 127 | \end{matrix}\right. 128 | \end{align*} 129 | \end{definition} 130 | 131 | 利用勒让德符号,二次剩余的乘法法则可用一个公式表示。 132 | 133 | \begin{theorem}{二次剩余乘法法则--表达方式2}{residue2} 134 | 设$p$为素数,则 135 | $$ 136 | \left(\frac{a}{p}\right)\left(\frac{b}{p}\right)=\left(\frac{ab}{p}\right) 137 | $$ 138 | \end{theorem} 139 | 140 | 勒让德符号使计算可以更直观,比如 141 | $$ 142 | \left(\frac{75}{97}\right)=\left(\frac{3\cdot5\cdot5}{97}\right)=\left(\frac{3}{97}\right)\left(\frac{5}{97}\right)\left(\frac{5}{97}\right) 143 | $$ 144 | 而 145 | $$ 146 | \left(\frac{5}{97}\right)\left(\frac{5}{97}\right)=1 147 | $$ 148 | 所以 149 | $$ 150 | \left(\frac{75}{97}\right)=\left(\frac{3}{97}\right)=1 \quad (3\ is\ QR) 151 | $$ 152 | 153 | 这里能够判断出3是模97的一个$QR$有些幸运($10^2$),我们似乎还没有回答如何快速计算一个数是否是二次剩余,即如何快速计算勒让德符号。 154 | 155 | \section{二次互反律} 156 | 通过前一节的讨论,我们清楚了对于任何一个奇素数,$[1,p-1]$有一半是二次剩余。 157 | 现在先换个角度,考虑对于一个数$a$,看看对于哪些$p$,这个数是$QR$。 158 | 159 | 我们先令$a=-1$,看对于哪些素数$p$,同余式$x^2\equiv -1\ (mod \ p)$有解。 160 | 或者说,对哪些素数,$\left( \frac{-1}{p} \right)=1$。同样,通过列出小的数据,可以看出, 161 | {\heiti 若$p$与1模4同余,则$-1$似乎是$p$的$QR$;若$p$与3模4同余,则$-1$似乎是$NR$。} 162 | 163 | 用来证明这个猜想的工具称为“费马小定理的平方根”,即考虑$A=a^\frac{p-1}{2} \ mod \ p$值为多少? 164 | 165 | \begin{theorem}{欧拉准则}{label} 166 | 设$p$为素数,则 167 | $$ 168 | a^\frac{p-1}{2}\equiv \left( \frac{a}{p} \right) \quad mod \ p 169 | $$ 170 | \end{theorem} 171 | 172 | \begin{proof} 173 | 令$A=a^\frac{p-1}{2}$,显然$A^2\equiv 1 \ mod \ p$,因此$p$整除$(A-1)(A+1)$。 174 | 从而$p$要么整除$(A-1)$要么整除$(A+1)$,因此$A$要么和$1$模$p$同余,要么和$-1$。 175 | 176 | 177 | 当$a$是$QR$时,则$a\equiv g^{2k}\ (mod \ p)$ ,则$a^{\frac{p-1}{2}}\equiv (g^{p-1})^k\equiv 1\ (mod \ p)$; 178 | 当$a$是$NR$时,则$a\equiv g^{2k+1} \ (mod \ p)$,则$a^{\frac{p-1}{2}}\equiv g^{\frac{p-1}{2}} \ (mod \ p)$。 179 | 由前面讨论知道,$a^{\frac{p-1}{2}}$要么和$1$模$p$同余,要么和$-1$。这里由于$g$是原根,则和$1$模$p$同余的最小次幂只能是$p-1$。 180 | 所以这里$a^{\frac{p-1}{2}}\equiv -1 \ (mod \ p)$。 181 | 证毕。 182 | \end{proof} 183 | 184 | 185 | 有了欧拉准则,就可以轻松的判断$-1$是不是$p$的二次剩余了: 186 | 187 | \begin{theorem}{二次互反律---part one}{label} 188 | 189 | 设$p$为素数,{\heiti 若$p$与1模4同余,则$-1$是$p$的$QR$;若$p$与3模4同余,则$-1$是$NR$。} 190 | 191 | 用勒让德符号表示如下: 192 | $$ 193 | \left( \frac{-1}{p} \right)= \left\{\begin{matrix} 194 | 1 \quad if\ p\equiv 1 \ (mod \ 4) \\ 195 | -1 \quad if\ p\equiv 3 \ (mod \ 4) 196 | \end{matrix}\right. 197 | $$ 198 | \end{theorem} 199 | 200 | \begin{proof} 201 | 带入欧拉准则即证。 202 | \end{proof} 203 | 204 | \vbox{} 205 | 206 | 下面考虑$a=2$的情况。如果直接使用欧拉准则,即$2^{\frac{p-1}{2}} \ mod \ p$,似乎不能看出结果是1还是-1。 207 | 高斯提出了一个方法,可以简单地知道$2^{\frac{p-1}{2}} \ mod \ p$的值是-1还是1,其结论和二次互反律part one一样简单: 208 | 209 | 210 | \begin{theorem}{二次互反律---part two}{label} 211 | $$ 212 | \left( \frac{2}{p} \right)= \left\{\begin{matrix} 213 | 1 \quad if\ p\equiv 1\ or\ 7 \ (mod \ 8) \\ 214 | -1 \quad if\ p\equiv 3\ or\ 5\ (mod \ 8) 215 | \end{matrix}\right. 216 | $$ 217 | \end{theorem} 218 | 219 | \begin{proof} 220 | 利用欧拉准则知,需要寻找$2^{\frac{p-1}{2}} \ mod \ p$结果的模式。 221 | $p$是一个素数,令$P=\frac{p-1}{2}$,从偶数$2,4,6,...,p-1$开始,将它们相乘,并从每个数中提出2因子,可得 222 | $$ 223 | 2*4*6*\cdots*(p-1)=2^P*P! 224 | $$ 225 | 然后再对$2,4,6,...,p-1$进行模$p$化简,使其全部落在$[-P,P]$之间,乘起来。比较这两个乘积,可得 226 | $$ 227 | 2^P*P!\equiv (-1)^{Number\ of\ negative\ signs}* P! \quad (mod \ p) 228 | $$ 229 | 负号的个数是指对$2,4,6,...,p-1$进行模$p$化简后落在$[-P,-1]$之间的个数。消去$P!$,得 230 | $$ 231 | 2^{\frac{p-1}{2}}\equiv (-1)^{Number\ of\ negative\ signs} \quad (mod \ p) 232 | $$ 233 | 于是当负数的个数为偶数时,$2$是$p$的二次剩余。而这里负数的个数和$p\ mod\ 8$相关,具体如定理中所示。 234 | \end{proof} 235 | 236 | \vbox{} 237 | 238 | 现在总结一下。对于一个给定的数$a$,我们要确定哪些素数$p$以$a$为二次剩余。上面解决了$a=-1$和$a=2$时的问题。 239 | 这个时候我们可以通过查看$p\%m$的一些结果得出$a$是否是$QR$或$NR$,且$m$较小,为$4$和$8$。 240 | 下面要解决的是其他$a$值的勒让德符号$(\frac{a}{p})$的计算问题。({\heiti 当然,你可以直接使用欧拉准则 241 | 去计算},但下面的方法还是很值得知晓) 242 | 243 | 例如,假设要计算$(\frac{70}{p})$,由前面的二次剩余乘法法则知,$(\frac{70}{p})=(\frac{2}{p})(\frac{5}{p})(\frac{7}{p})$, 244 | 怎么计算$(\frac{5}{p})$和$(\frac{7}{p})$呢? 245 | 246 | 概括来说,怎么计算$(\frac{q}{p})$呢?其中$q$也为素数。因为由乘法法则知,素数可以解决后,整数都可以解决。 247 | 248 | 通过打表观察后(此处略去500字......),可以得到对于一些$p,q$,有$\left( \frac{q}{p} \right)=\left( \frac{p}{q} \right)$;但有些不是,但不是的竟然都是$\left( \frac{q}{p} \right)=-\left( \frac{p}{q} \right)$。 249 | 这其中有没有模式呢?有的。 250 | 251 | \begin{theorem}{二次互反律}{label} 252 | $$ 253 | \left( \frac{-1}{p} \right)= \left\{\begin{matrix} 254 | 1 \quad if\ p\equiv 1 \ (mod \ 4) \\ 255 | -1 \quad if\ p\equiv 3 \ (mod \ 4) 256 | \end{matrix}\right. 257 | $$ 258 | $$ 259 | \left( \frac{2}{p} \right)= \left\{\begin{matrix} 260 | 1 \quad if\ p\equiv 1\ or\ 7 \ (mod \ 8) \\ 261 | -1 \quad if\ p\equiv 3\ or\ 5\ (mod \ 8) 262 | \end{matrix}\right. 263 | $$ 264 | $$ 265 | \left(\frac{q}{p}\right)=\left\{\begin{array}{c}{\left(\frac{p}{q}\right)}\quad if\ p\equiv1\ (mod\ 4)\ or\ q\equiv1\ (mod\ 4) \\ {-\left(\frac{p}{q}\right) \quad if\ p\equiv3\ (mod\ 4)\ and\ q\equiv3\ (mod\ 4) }\end{array}\right. 266 | $$ 267 | \end{theorem} 268 | 269 | \begin{proof} 270 | 前两部分已经证明,最后一个部分感兴趣的读者可以自行查阅资料。 271 | \end{proof} 272 | 273 | \vbox{} 274 | 275 | 二次互反律不仅很美,也很实用。 276 | 它使我们可以翻转勒让德符号$\left(\frac{q}{p}\right)$,用$\pm \left(\frac{p}{q}\right)$来替代它,然后可以模$q$化简$p$,并不断重复此过程,这样会使$p,q$急剧下降。 277 | 278 | 所以,计算勒让德符号的困难之处不是二次互反律的使用,而是对数字的因式分解。 279 | 280 | 如果不分解,继续做下去,这样答案是否正确呢? 281 | 282 | {\heiti 正确!} 也就是说对于$\left(\frac{q}{p}\right)$,之前是$p,q$为素数,现在是对任意的正奇数$a$和$b$,可以给勒让德符号$\left(\frac{a}{b}\right)$指定一个值,反复使用广义二次互反定律来计算结果。这种广义勒让德符号常称作{\heiti 雅克比符号}。 283 | 284 | \begin{theorem}{广义二次互反律}{label} 285 | 设$a,b$为正奇数,则 286 | $$ 287 | \left( \frac{-1}{b} \right)= \left\{\begin{matrix} 288 | 1 \quad if\ b\equiv 1 \ (mod \ 4) \\ 289 | -1 \quad if\ b\equiv 3 \ (mod \ 4) 290 | \end{matrix}\right. 291 | $$ 292 | $$ 293 | \left( \frac{2}{b} \right)= \left\{\begin{matrix} 294 | 1 \quad if\ b\equiv 1\ or\ 7 \ (mod \ 8) \\ 295 | -1 \quad if\ b\equiv 3\ or\ 5\ (mod \ 8) 296 | \end{matrix}\right. 297 | $$ 298 | $$ 299 | \left(\frac{a}{b}\right)=\left\{\begin{array}{c}{\left(\frac{b}{a}\right)}\quad if\ a\equiv1\ (mod\ 4)\ or\ b\equiv1\ (mod\ 4) \\ {-\left(\frac{b}{a}\right) \quad if\ a\equiv3\ (mod\ 4)\ and\ b\equiv3\ (mod\ 4) }\end{array}\right. 300 | $$ 301 | \end{theorem} 302 | 303 | \begin{note} 304 | 只允许当$a$是正奇数的时候翻转$a,b$,这一点极为重要。当$a$是偶数时,可以分解出因子2;当$a$是负数时,可以分解出因子$-1$。 305 | \end{note} 306 | 307 | \vbox{} 308 | 309 | 使用二次互反律求解勒让德符号,时间复杂度 $O(logb)$。 310 | 311 | {\heiti 当然也可以直接用欧拉准则,快速幂即可。} 312 | 313 | \lstinputlisting[language=C++, style=codestyle2]{code04/Legendre.cpp} 314 | 315 | 现在我们可以快速计算勒让德符号了(两种方法)。{\heiti 但知道有解还是不够的,往往我们想知道解是什么。} 316 | 317 | 318 | \section{求解二次剩余} 319 | Cipolla's algorithm是求解二次剩余的经典方法。 320 | \begin{theorem}{Cipolla's algorithm}{Cipolla} 321 | 现有同余式$x^2\equiv n\ (mod\ p)$,其中$x,n\in \mathcal{F}_p$,$p$是奇素数,$\mathcal{F}_p$是有$p$个元素的有限域:$\{0,1,...,p-1\}$。 322 | \begin{enumerate} 323 | \item 寻找一个$a\in \mathcal{F}_p$,使得$a^2-n$是非二次剩余。(随机寻找即可,期望随机次数为2) 324 | \item 在域$\mathcal{F}_{p^2}=\mathcal{F}_p(\sqrt{a^2-n})$中计算$x = (a+\sqrt{a^2-n})^{(p+1)/2}$即为满足$x^2=n$的一个解。 325 | \end{enumerate} 326 | \end{theorem} 327 | 328 | 在证明前,我们先来看一个例子。注意第二步骤之前所有元素都是在域$\mathcal{F}_{13}$中,第二步中在域$\mathcal{F}_{13^2}$中。 329 | 330 | 寻找$x^2=10$的所有解。 331 | 332 | 在执行算法前,你可以先用欧拉准则或者二次互反律计算一下$\left( \frac{10}{13} \right)$是否为1,若不是1,则无解;若是1,执行下面算法。 333 | 334 | \begin{enumerate} 335 | \item 随机寻找一个$a\in \mathcal{F}_{13}$,使得$a^2-10$是非二次剩余。假如随机到$a=2$,则$a^2-10$为$7$,$\left( \frac{7}{13} \right)=-1$, 336 | 满足要求。 337 | \item 计算$x = (a+\sqrt{a^2-n})^{(p+1)/2} = (2+\sqrt{-6})^7$。 338 | $$ 339 | \begin{array}{l}{(2+\sqrt{-6})^{2}=4+4 \sqrt{-6}-6=-2+4 \sqrt{-6}} \\ {(2+\sqrt{-6})^{4}=(-2+4 \sqrt{-6})^{2}=-1-3 \sqrt{-6}} \\ {(2+\sqrt{-6})^{6}=(-2+4 \sqrt{-6})(-1-3 \sqrt{-6})=9+2 \sqrt{-6}} \\ {(2+\sqrt{-6})^{7}=(9+2 \sqrt{-6})(2+\sqrt{-6})=6}\end{array} 340 | $$ 341 | 所以$x=6$是一个解,当然$(-6)\ mod\ 13 = 7$也是一个解。 342 | \end{enumerate} 343 | 344 | \vbox{} 345 | 346 | \begin{proof} 347 | Cipolla's algorithm正确性的证明。 348 | 349 | {\heiti Part I\quad 证明$\mathcal{F}_{p^2}=\mathcal{F}_p(\sqrt{a^2-n})=\{x+y\sqrt{a^2-n}:x,y\in \mathcal{F}_p\}$确实是一个域。} 350 | 351 | 为了记号方便,令$\omega = \sqrt{a^2-n}$,由于$a^2-n$是非二次剩余,所以在$\mathcal{F}_{p}$中是没有平方根的。这里$\omega$可以类比复数域中的$i$。 352 | 353 | $\mathcal{F}_{p^2}$中的加法被定义为: 354 | $$ 355 | \left(x_{1}+y_{1} \omega\right)+\left(x_{2}+y_{2} \omega\right)=\left(x_{1}+x_{2}\right)+\left(y_{1}+y_{2}\right) \omega 356 | $$ 357 | 358 | 乘法被定义为: 359 | $$ 360 | \left(x_{1}+y_{1} \omega\right)\left(x_{2}+y_{2} \omega\right)=x_{1} x_{2}+x_{1} y_{2} \omega+y_{1} x_{2} \omega+y_{1} y_{2} \omega^{2}=\left(x_{1} x_{2}+y_{1} y_{2}\left(a^{2}-n\right)\right)+\left(x_{1} y_{2}+y_{1} x_{2}\right) \omega 361 | $$ 362 | 363 | 可交换、可结合、可分配都比较显然。加法幺元是$0+0\omega$,乘法幺元是$1+0\omega$。 364 | 下面证明加法和乘法逆元的存在。显然$x+y\omega$的加法逆元是$-x-y\omega$。对于乘法,记$\alpha = x_1+y_1\omega,\ \alpha^{-1}=x_{2}+y_{2} \omega$,则有: 365 | $$ 366 | \left(x_{1}+y_{1} \omega\right)\left(x_{2}+y_{2} \omega\right)=\left(x_{1} x_{2}+y_{1} y_{2}\left(a^{2}-n\right)\right)+\left(x_{1} y_{2}+y_{1} x_{2}\right) \omega=1 367 | $$ 368 | 369 | 通过对应系数可得两个方程: 370 | $$ 371 | \left\{\begin{array}{l}{x_{1} x_{2}+y_{1} y_{2}\left(a^{2}-n\right)=1} \\ {x_{1} y_{2}+y_{1} x_{2}=0}\end{array}\right. 372 | $$ 373 | 374 | 解得: 375 | $$ 376 | \begin{array}{l}{x_{2}=-y_{1}^{-1} x_{1}\left(y_{1}\left(a^{2}-n\right)-x_{1}^{2} y_{1}^{-1}\right)^{-1}} \\ {y_{2}=\left(y_{1}\left(a^{2}-n\right)-x_{1}^{2} y_{1}^{-1}\right)^{-1}}\end{array} 377 | $$ 378 | 379 | 也就是说$x_2,\ y_2$确实存在,因为其表达式中的元素均在$\mathcal{F}_{p}$中。 380 | 381 | Part I证毕,$\mathcal{F}_{p^2}$确实是一个域。 382 | 383 | \vbox{} 384 | 385 | {\heiti Part II\quad 证明对于域中的每个元素,有$x+y \omega \in \mathcal{F}_{p^{2}}:(x+y \omega)^{p}=x-y \omega$。} 386 | 387 | 首先在模$p$意义下有$(a+b)^p = a^p + b^p$,$x^p=x$,$\omega^{p-1}=\left(\omega^{2}\right)^{\frac{p-1}{2}}=-1 \ $ ($\omega^2=a^2-n$是非二次剩余,欧拉准则)。 388 | 所以 389 | $$ 390 | (x+y \omega)^{p}=x^{p}+y^{p} \omega^{p}=x-y \omega 391 | $$ 392 | 393 | Part II证毕。 394 | 395 | \vbox{} 396 | 397 | {\heiti Part III\quad 证明若$x_{0}=(a+\omega)^{\frac{p+1}{2}} \in \mathcal{F}_{p^{2}}$,则有$x_{0}^{2}=n \in \mathcal{F}_{p}$。} 398 | 399 | $$ 400 | x_{0}^{2}=(a+\omega)^{p+1}=(a+\omega)(a+\omega)^{p}=(a+\omega)(a-\omega)=a^{2}-\omega^{2}=a^{2}-\left(a^{2}-n\right)=n 401 | $$ 402 | 403 | 注意上面的计算都发生在域$\mathcal{F}_{p^{2}}$中,所以$x_0\in \mathcal{F}_{p^{2}}$。由拉格朗日定理,在任何域上,$n$阶多项式最多有$n$个根。在$\mathcal{F}_{p}$上,$x^2-n$有两个 404 | 根,这些根在$\mathcal{F}_{p^{2}}$上同样也是根。所以在$\mathcal{F}_{p^{2}}$上$x^2-n$的两个根$x_0,\ -x_0$也是$\mathcal{F}_{p}$上$x^2-n$的根。 405 | 406 | Part III证毕。 407 | 408 | 更多资料可以查看\href{https://en.wikipedia.org/wiki/Cipolla\%27s_algorithm}{https://en.wikipedia.org/wiki/Cipolla\%27s\_algorithm} 409 | \end{proof} 410 | 411 | \vbox{} 412 | 413 | {\heiti 时间复杂度:$O(logp)$,常数较大,因为域$\mathcal{F}_{p^{2}}$中运算取模较多。} 414 | 415 | 模数为素数。 416 | \lstinputlisting[language=C++, style=codestyle2]{code04/quad-res.cpp} 417 | 418 | \vbox{} 419 | 420 | {\heiti 如果模数是质数幂呢?} 421 | 422 | 设$q=p^k$,考虑求解$x^2\equiv n\ (mod\ q)$。当$n\equiv 0\ (mod\ p^k)$时,解为$x \equiv 0\left(\bmod p^{\lceil k / 2\rceil}\right)$。 423 | 否则可令$n=p^{r} a,\ p \nmid a,\ 0 \leqslant r2$两种情况讨论。 426 | 427 | \begin{enumerate} 428 | \item {\heiti 模2的幂。}$x^{2} \equiv a(\bmod\ q), q=2^{k}, 2\nmid a$。$q=4$时,有解当且仅当$a\equiv 1\ (mod\ 4)$,解为$x\equiv 1,3\ (mod\ 4)$。 429 | 430 | 下面考虑$q\ge8$的情况。由于任意奇数的平方模$8$余1,于是需有$a\equiv 1\ (mod\ 8)$。此时,$x^2\equiv a\ (mod\ q)$恰有4个解$\pm x_{k}, \pm\left(q / 2-x_{k}\right)$。 431 | \begin{proof} 432 | $q=8$时,4个解分别为$x \equiv \pm 1, \pm 3(\bmod 8)$。下面进行归纳。设$x^{2} \equiv a\left(\bmod\ 2^{k}\right)$的4个解为$\pm x_{k}, \pm\left(2^{k-1}-x_{k}\right)$,则 433 | $$ 434 | \begin{aligned} & x_{k}^{2}-\left(2^{k-1}-x_{k}\right)^{2} \\=&\left(2 x_{k}-2^{k-1}\right) 2^{k-1} \\ \equiv & x_{k} 2^{k} \quad\left(\bmod\ 2^{k+1}\right) \\ \equiv & 2^{k} \quad\left(\bmod\ 2^{k+1}\right) \end{aligned} 435 | $$ 436 | 于是$x_{k}, 2^{k-1}-x_{k}$中恰有一个可以作为$x_{k+1}$,使得$x_{k+1}^{2} \equiv a\left(\bmod\ 2^{k+1}\right)$。又由$\left(2^{k}-x_{k+1}\right)^{2} \equiv x_{k+1}^{2}\left(\bmod\ 2^{k+1}\right)$即得另外两个解。证毕。 437 | \end{proof} 438 | 439 | \item {\heiti 模奇素数的幂。}$x^{2} \equiv a(\bmod\ q), q=p^{k}, p \nmid a$。当且仅当$a$是$p$的二次剩余时,上式有解。有解时恰有两个解$\pm x_{k}$。 440 | \begin{proof} 441 | 使用归纳法。若$x_{k}^{2} \equiv a\left(\bmod\ p^{k}\right)$,设 442 | $$ 443 | \begin{aligned} x_{k+1} &=x_{k}+t p^{k}\left(\bmod\ p^{k+1}\right), t=0,1, \cdots, p-1 \\ x_{k+1}^{2} &=x_{k}^{2}+t^{2} p^{2 k}+2 x_{k} t p^{k} \\ & \equiv x_{k}^{2}+2 x_{k} t p^{k}\left(\bmod\ p^{k+1}\right) \\ & \equiv a\left(\bmod\ p^{k+1}\right) \end{aligned} 444 | $$ 445 | 于是 446 | $$ 447 | 2x_kt\equiv (a-x_k^2)/p^k\ (mod\ p) 448 | $$ 449 | 解出$t$即可得到对应的$x_{k+1}$。 450 | 证毕。 451 | \end{proof} 452 | \end{enumerate} 453 | 454 | \vbox{} 455 | 456 | {\heiti 如果模数是合数,将模数分解后分别计算,再用中国剩余定理合并。} 457 | 458 | 参考资料:\href{https://max.book118.com/html/2018/0525/168640677.shtm}{jcvb\quad 二次剩余相关} 459 | 460 | \section{求解N次剩余} 461 | \begin{custom}{问题} 462 | 给定$N,a,p$,求出$x^N\equiv a\ (mod \ p)$在模$p$意义下的所有解(其中$p$是素数) 。 463 | \end{custom} 464 | 465 | \begin{solution} 466 | 如果能找到原根$g$,则$\{1,2,..,.p-1\}$与$\{g^1,g^2,...,g^{p-1}\}$之间就建立了双射关系。 467 | 468 | 令$g^y=x,\ g^t=a$,$x^N\equiv a\ (mod \ p)$ ,则有 469 | $$ 470 | g^{y*N}\equiv g^t \quad (mod \ p) 471 | $$ 472 | 因为$p$是素数,所以方程左右都不会为0。原问题转化为: 473 | $$ 474 | N*y\equiv t\ (mod \ (p-1)) 475 | $$ 476 | 由于$N,p$已知,则上式为解模线性方程。 477 | 478 | 而$t$的值,由$g^t\equiv a \ (mod\ p)$,用解离散对数的方法求出。 479 | \end{solution} 480 | 481 | 输入:$1 \le a,\ N < p \le 10^9$,$p$为素数。 482 | 483 | {\heiti 时间复杂度} $O(\sqrt{p}log(\sqrt{p}))$ 484 | 485 | \lstinputlisting[language=C++, style=codestyle2]{code04/N-res.cpp} 486 | 487 | {\heiti 如果模数是非素数呢?} 488 | 489 | 模数非素数:时间和素数一样,甚至更低,因为分解质因数处理的时候规模较小,最后中国剩余定理合并。 490 | \lstinputlisting[language=C++, style=codestyle2]{code04/N-res-notprime.cpp} 491 | 492 | \begin{example} 493 | 已知$x^{2^{30}+3}\ mod\ n = c$,给定$c,n$,其中$n$是两个相邻素数($p \in [10^5, 10^9]$)的乘积。求解$x$,题目保证在模意义下只有一个解。 494 | $10^5$组测试数据。 495 | (\href{https://codeforces.com/gym/102055/problem/K}{CCPC2018-Final Mr. Panda and Kakin}) 496 | \end{example} 497 | 498 | \begin{solution} 499 | 注意到这里$2^{30}+3$和$\phi(n)$一定互质,所以可以直接求$2^{30}+3$模$\phi(n)$下的逆元,最后两边做逆元次方即得答案。 500 | 501 | 注意要使用$O(1)$快速乘,否则超时。 502 | \end{solution} 503 | 504 | \lstinputlisting[language=C++, style=codestyle2]{code04/Kakin.cpp} 505 | 506 | 507 | \vbox{} 508 | 509 | \vbox{} 510 | 511 | \begin{problemset} 512 | \item \href{https://www.luogu.org/problem/P5491}{二次剩余 \quad 洛谷P5491 \quad 模板} 513 | \item \href{https://www.51nod.com/Challenge/Problem.html#problemId=1123}{任意模数N次剩余 \quad 51nod 1123 \quad 模板} 514 | \end{problemset} -------------------------------------------------------------------------------- /06indefiniteequation.tex: -------------------------------------------------------------------------------- 1 | \chapter{不定方程} 2 | 3 | \begin{introduction} 4 | \item 佩尔方程 5 | \item 其它不定方程 6 | \end{introduction} 7 | 8 | \section{佩尔方程} 9 | $Pell$方程是指具有形式$x^2-Dy^2=1$的方程,其中$D$是一个固定的正整数且{\heiti 不是完全平方数}。 10 | 11 | 假设可求得$Pell$方程的一个解$x_1,y_1$,则可以用下面的方法来产生一个新的解。将已知解因式分解为 12 | $$ 13 | 1=x_1^2-Dy_1^2=(x_1+y_1\sqrt{D})(x_1-y_1\sqrt{D}) 14 | $$ 15 | 两边同时平方便得到一个新的解: 16 | $$ 17 | 1=1^2=(x_1+y_1\sqrt{D})^2(x_1-y_1\sqrt{D})^2\\ 18 | =(x_1^2+y_1^2D)^2-(2x_1y_1)^2D 19 | $$ 20 | 也就是说,$(x_1^2+y_1^2D\ ,\ 2x_1y_1)$是一个新解。取3次幂、4次幂,等等,可以找到所需的任意多个解。 21 | 22 | \begin{theorem}{Pell方程定理}{label} 23 | 设$D$是一个正整数,且不是完全平方数,则佩尔方程 24 | $$ 25 | x^2-Dy^2=1 26 | $$ 27 | 总有正整数解。如果$(x_1,y_1)$是使$x_1$最小的解,则每个解$(x_k,y_k)$可通过取幂得到: 28 | $$ 29 | x_k+y_k\sqrt{D}=(x_1+y_1\sqrt{D})^k ,\quad k=1,2,3,... 30 | $$ 31 | 矩阵形式求解: 32 | $$ 33 | \left(\begin{array}{l}{x_{n}} \\ {y_{n}}\end{array}\right)=\left(\begin{array}{ll}{x_{1}} & {D y_{1}} \\ {y_{1}} & {x_{1}}\end{array}\right)^{n-1}\left(\begin{array}{l}{x_{1}} \\ {y_{1}}\end{array}\right) 34 | $$ 35 | \end{theorem} 36 | \begin{note} 37 | 注意,一些方程的最小解会很大。关于何时最小解大,何时小,还没有已知的明确的模式。 38 | \end{note} 39 | 40 | 41 | \section{其它不定方程} 42 | 43 | \subsection{OEIS A011772} 44 | $f(n)=$最小的正整数$m$使得$\frac{m*(m+1)}{2}$是$n$的倍数。$f(1)\sim f(10)$分别为$1, 3, 2, 7, 4, 3, 6, 15, 8, 4$。现在 45 | 给定$n,\ 1\le n\le 10^{12}$,求$f(n)$。 46 | 47 | 题目链接:\href{https://www.cometoj.com/contest/65/problem/C?problem_id=3684}{鱼跃龙门} 48 | 49 | \begin{solution} 50 | 由于$m*(m+1)$一定整除2,于是问题就是找最小的$m$,使得$m*(m+1)$是$2*n$的倍数,那我们假设是$k$倍,那就是解二元二次方程$m*(m+1) = 2nk$中$m$的最小正整数解。 51 | 乍一看很难做,这里需要用到$m$和$m+1$一定互质的性质。我们将$2n$质因数分解,然后分成$A,B$两块(暴力枚举),即$2n = A*B,\ gcd(A,B)=1$。 52 | 然后设$m=Ax,\ m+1=By$,那么有$By-Ax=1,\ y=\frac{Ax+1}{B}$。我们的目的是让$k(=xy)$尽量小,由于$y$随$x$单增,所以只要$x$最小即可。 53 | 那么只要解$AX\equiv -1 (mod B)$的最小正整数解即可。 54 | 55 | 时间复杂度为$O(\frac{\sqrt{2n}}{ln(\sqrt{2n})}+2^{12}*log(n))$。 56 | \end{solution} 57 | 58 | \lstinputlisting[language=C++, style=codestyle2]{code06/OEIS-A011772.cpp} 59 | 60 | 61 | \vbox{} 62 | 63 | \vbox{} 64 | 65 | \begin{problemset} 66 | \item \href{http://poj.org/problem?id=1320}{POJ1320 \quad Street Numbers\quad 佩尔方程} 67 | \item \href{http://acm.hdu.edu.cn/showproblem.php?pid=6222}{HDU6222 \quad Heron and His Triangle\quad 佩尔方程} 68 | \end{problemset} -------------------------------------------------------------------------------- /07others.tex: -------------------------------------------------------------------------------- 1 | \chapter{其他} 2 | \begin{introduction}[本章内容提要] 3 | \item 勾股数组 4 | \item 圆上整点与高斯素数 5 | \item 模线性方程循环节 6 | \item 分数转小数 7 | \item DFS Similar 8 | \item FFT, NTT 9 | %\item 多项式相关 10 | \end{introduction} 11 | 12 | \section{勾股数组} 13 | 14 | \begin{definition}{本原勾股数组}{label} 15 | 本原勾股数组是一个三元组$(a,b,c)$,其中$a,b,c$没有公因数,且满足: 16 | $$a^2 + b^2 = c^2$$ 17 | \end{definition} 18 | 19 | \begin{theorem}{勾股数组定理}{gougushuzu} 20 | 每个本原勾股数组$(a,b,c)\ $(a为奇数,b为偶数)都可从如下公式得出: 21 | $$ 22 | a=st ,\quad b=\frac{s^2-t^2}{2},\quad c=\frac{s^2+t^2}{2} 23 | $$ 24 | 其中$s>t>=1$是任意没有公因数的奇数。 25 | \end{theorem} 26 | 27 | 28 | \section{平方数之和、圆上整点} 29 | 这一节要解决的问题是给定一个数$x$,判断它能否分解为两个整数的平方之和,以及具体来说如何分解。从几何意义上考虑就是以原点为圆心,$\sqrt{x}$为半径的圆,是否经过坐标点(横纵坐标都是整数), 30 | 以及具体是哪些点。 31 | 32 | 先从$x$为素数考虑,这个时候规律相对简单。 33 | \subsection{将素数分解为平方之和} 34 | 35 | 36 | 37 | \begin{theorem}{定理}{primesquare} 38 | 设$p$是素数,则$p$是两平方数之和的充要条件是$p\equiv 1 \ (mod \ 4) $ 或$p=2$。 39 | \end{theorem} 40 | 41 | \begin{proof} 42 | 这里有两个断言: 43 | \begin{itemize} 44 | \item $p$是两平方数之和; 45 | \item $p\equiv 1\ (mod\ 4)$ 或者 $p=2$。 46 | \end{itemize} 47 | 48 | 要证明充要性有两个方面,即用一个断言作为条件去证明另外一个,下面分别证明。 49 | \begin{enumerate} 50 | \item 若$p$是两平方数之和,则$-a^2\equiv b^2 (mod \ p)$ ,接着对两边取勒让德符号:(回顾第四章的内容) 51 | \begin{align*} 52 | \left(\frac{-1}{p}\right)\left(\frac{a}{p}\right)^2&=\left(\frac{b}{p}\right)^2 \\ 53 | \left(\frac{-1}{p}\right)&=1 54 | \end{align*} 55 | 于是由二次互反律知,$p$模4余1或者$p=2$。$\square$ 56 | \item 当$p\equiv 1\ (mod\ 4)$或$p=2$时,要证明$p$一定可以表示成两平方数之和。也就是说只要我们能想到一种方法总能找到如何分解,就完成了证明。下面介绍一种方法, 57 | 称之为{\heiti 费马降阶法(Fermat Descent Procedure)}。我们后面的代码就是基于此方法。 58 | 59 | 我们从$A^2+B^2=Mp$开始,如果这里$M=1$,则证明完毕。所以我们考虑$M\ge 2$。 60 | 费马的想法是,我们用现有的$A,B,M$,要是能构造出$a^2+b^2=mp \ \&\&\ m\le M-1$, 61 | 则迭代下去,$m=1$时就完成了构造。 62 | 63 | 在说具体的构造方法之前,先看一个恒等式,其正确性是显然的,在下面的构造过程中,这个恒等式起到关键作用。 64 | $$ 65 | (u^2+v^2)(A^2+B^2)=(uA+vB)^2+(vA-uB)^2 66 | $$ 67 | 费马降阶法的过程如表\ref{tab:fermat-descent}所示: 68 | \begin{table}[!htbp] 69 | \centering 70 | \caption{费马降阶法 \label{tab:fermat-descent}} 71 | \begin{spacing}{1.5} 72 | \begin{tabular}{|c|c|} 73 | \toprule[1pt] 74 | 举例(p=881) & 符号表示\quad p any prime $\equiv$ 1($mod\ 4$) \\ 75 | \midrule[2pt] 76 | 有$387^2+1^2=170*881$,$\ 170<881$ & 有$A^2+B^2=Mp$,$\ M0 159 | \end{align*} 160 | 对于半径为$\sqrt{N}$的圆,圆上整点的数目(即将$N$分成两个平方数之和的方案数)可以这样计算: 161 | 162 | 将$N$质因数分解: 163 | $$ 164 | N = p_1^{k_1}*p_2^{k_2}*...*p_m^{k_m} 165 | $$ 166 | 则圆上整点数目 = $4*\Pi_{i=1}^{m}(\sum_{j=0}^{k_i}\chi(p_i^j))$。 167 | 168 | 上面的这个式子只是为了统一,所以看起来规律不明显。实际上一句话: 169 | 170 | 如果有模4余3的素数的指数为奇数,则答案为0;否则就是所有模4余1的素数的指数加1后乘起来最后再乘4。 171 | \end{theorem} 172 | 173 | \vbox{} 174 | 175 | 至于具体如何计算分解的方案,推荐大家看上面那个视频。大致思路就是对于可分解的素数利用费马降阶法进行分解,然后再在复数域中对不同质数 176 | 的结果组合计算一下。下面给出代码: 177 | 178 | 输入一个半径$r$,输出在圆上的所有整点。 179 | 180 | {\heiti 时间复杂度:$O(A+B)$,其中$A$为质因子分解$r^2(thus\ r)$的时间,$B$为圆上整点数。 181 | 182 | 该代码在$r\le 10^9$时通过测试,更大时注意下会不会爆范围。} 183 | 184 | \href{https://nanti.jisuanke.com/t/41421}{圆上整点/高斯素数 \quad 2019上海网络赛 Peekaboo} 185 | 186 | \lstinputlisting[language=C++, style=codestyle2]{code07/Lattice-points.cpp} 187 | 188 | 189 | 190 | 191 | 192 | 193 | \section{循环节问题} 194 | \subsection{二阶常系数齐次线性递推循环节} 195 | 所谓二阶常系数齐次线性递推式就是类似斐波那契递推式那样的数列。这样的数列在模意义下会存在循环节。下面阐述下如何求其循环节: 196 | 197 | 198 | 先将模数分解,在模素数幂意义下分别求循环节,最后取最小公倍数即可。而对于模素数幂有结论$G(p_i^{a_i}) = p_i^{a_1-1}G(p_i)$。$G(m)$表示在模数为$m$时的循环节大小。 199 | 200 | 所以问题转为模素数如何处理。即给定$a,b,f(1),f(2)$,且满足$f(n)=a*f(n-1) + b*f(n-2)$。求$f(n)\ mod\ p$的循环节。 201 | 202 | 写成矩阵形式,如下: 203 | $$ 204 | \left[\begin{array}{c}{f(n)} \\ {f(n-1)}\end{array}\right]=\left[\begin{array}{cc}{a} & {b} \\ {1} & {0}\end{array}\right]\left[\begin{array}{c}{f(n-1)} \\ {f(n-2)}\end{array}\right] 205 | $$ 206 | 207 | 变形一下: 208 | $$ 209 | \left[\begin{array}{c}{f(n+2)} \\ {f(n+1)}\end{array}\right]=\left[\begin{array}{cc}{a} & {b} \\ {1} & {0}\end{array}\right]^{n}\left[\begin{array}{c}{f(2)} \\ {f(1)}\end{array}\right] 210 | $$ 211 | 那么现在的问题就转化为求最小的$n$,使得 212 | $$ 213 | \left[\begin{array}{ll}{a} & {b} \\ {1} & {0}\end{array}\right]^{n}(\bmod p)=\left[\begin{array}{ll}{1} & {0} \\ {0} & {1}\end{array}\right] 214 | $$ 215 | 216 | 如何快一点求$n$呢?这里直接给出结论:设$c=a^2+4b$, 有两种情况: 217 | \begin{enumerate} 218 | \item 若$c$是模$p$的二次剩余,则$n$是$p-1$的因子; 219 | \item 若$c$不是模$p$的二次剩余,则$n$是$(p+1)(p-1)$的因子 220 | \end{enumerate} 221 | 所以只要枚举因子并判断即可。 222 | 时间复杂度$O(T*2^3*log(p))$,其中$T$为$p-1$或$(p+1)(p-1)$的因子数目。 223 | 224 | \subsection{分数小数的循环节} 225 | \begin{custom}{问题} 226 | 给定一个分数$\frac{p}{q}$,将其转化为小数。 227 | \end{custom} 228 | 229 | \begin{solution} 230 | 考虑一个分数,有以下三种情况: 231 | \begin{itemize} 232 | \item 整数 233 | \item 有限小数 234 | \item 无限循环小数 235 | \end{itemize} 236 | 237 | 先来考虑下无限循环小数,我们先将$p$和$q$公共的因子除去,然后利用循环这个性质,可知存在$i,j$满足,$p*10^i \equiv p*10^j \ (mod \ q), \ where\ j>i\ge0$。 238 | {\heiti 这里$i$代表循环出现前有多少位数,$j-i$表示循环节长度。} 239 | 化简后得$p*10^i*(10^{j-i}-1) \equiv 0\ (mod \ q)$。也就是说,$q\ |\ p*10^i*(10^{j-i}-1)$。由于$10^{j-i}-1$中一定不存在因子$2$和$5$,所以要想是$q$的倍数, 240 | $2$和$5$的贡献均来自$10^i$。于是我们记录$q$中$2$的次幂数为$num2$,$5$的次幂数为$num5$,则$i = max(num2,num5)$。记$q$除去所有$2$和$5$因子后,为$m$,则$10^{j-i}\equiv 1 \ (mod\ m)$。 241 | 然后我们解同余方程$10^x\equiv 1\ (mod\ m),\ where\ x>0$,$j$就等于$x+i$。至于这个方程,$x$一定是$\varphi(m)$的因子,枚举因子判定即可。 242 | 243 | 如果$j$无解,表示该小数为有限小数,$i$也就表示了小数点后共有几位;如果$j$有解,含义如上。 244 | \end{solution} 245 | 246 | \href{https://www.luogu.org/problem/P1530}{luogu P1530 分数化小数} 247 | 248 | 时间复杂度:$\sqrt{q}*log(q)$ \quad (确定i, j) 249 | 250 | 输出格式: 按照下面规则,如果结果长度大于76,每行输出76个字符。 251 | \begin{itemize} 252 | \item 2/2 = 1.0 253 | \item 3/8 = 0.375 254 | \item 45/56 = 0.803(571428) 255 | \end{itemize} 256 | 257 | \lstinputlisting[language=C++, style=codestyle2]{code07/fraction2decimal.cpp} 258 | 259 | \begin{note} 260 | 由于上面这个题目要求输出时每行只要$76$个字符,我在写代码时一开始没有注意,因此直接使用的cout。所以代码中我使用了下面的方法: 261 | \lstinputlisting[language=C++, style=codestyle2]{code07/redirect-cout.cpp} 262 | 263 | 先将cout重定向到stringstream,然后从其中取出string,最后将cout还原到stdout。 264 | \end{note} 265 | 266 | \section{DFS Similar} 267 | dfs similar是指一类可以“暴力”搜索的问题,这类问题往往有很好的性质使得暴力的时间不会太长。 268 | 269 | \subsection{Counting Sequences I(18上海网络赛D)} 270 | \begin{custom}{问题} 271 | 我们定义一个由正整数组成的序列$a_1\ ,\ a_2\ ,\ ...\ ,\ a_n$是好的当: 272 | \begin{itemize} 273 | \item $n\ge2$ 274 | \item $a_1+...+a_n = a_1*...*a_n$ 275 | \end{itemize} 276 | 请输出有多少种这样的序列,$2\le n \le 3000$。 277 | \end{custom} 278 | 279 | \begin{solution} 280 | 考虑枚举序列中非1的数(非降序地),边枚举边统计不同长度序列的答案。直觉复杂度较低。可以证明一下,枚举的数的大小不超过3000。 281 | \end{solution} 282 | \lstinputlisting[language=C++, style=codestyle2]{code07/counting-sequences.cpp} 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | \subsection{反欧拉函数(洛谷P4780)} 291 | \begin{custom}{问题} 292 | 求最小的正整数$x$,使得$\phi(x)=n$。输出$x$,如果$x\ge 2^{31}$或者不存在则输出$-1$。 293 | \end{custom} 294 | 295 | \begin{solution} 296 | 枚举$x$的可能质因子$p$,则必须有$n\%(p-1)=0$ ,同时再枚举该质因子的指数,此时要求每有几个$p$(指数),$n$就要能整除$p$几次。 297 | 298 | 时间复杂度有点迷,但感觉再大点还是可以做的。 299 | \end{solution} 300 | \lstinputlisting[language=C++, style=codestyle2]{code07/Anti-Euler.cpp} 301 | 302 | 303 | 304 | 305 | 306 | 307 | \subsection{因子个数最多的数(51nod1060)} 308 | \begin{custom}{问题} 309 | 把一个数的约数个数定义为该数的复杂程度,给出一个$n$,求$1\sim n$中复杂程度最高的那个数。如果有多个数复杂度相等,输出最小的。 310 | $1\le n\le 10^{18}$。 311 | 312 | 即给定$N$,求$[1,N]$之间最大的反素数({\heiti 即拥有因子数目最多的数})。 313 | \end{custom} 314 | 315 | \begin{solution} 316 | 性质: 317 | \begin{itemize} 318 | \item 一个反素数的质因子们必然是从2开始连续的质数。 319 | \item $p=2^{t_1} * 3^{t_2} * 5^{t_3} * 7^{t_4}$...必然$t_1 \ge t_2 \ge t_3 \ge ...$ 320 | \end{itemize} 321 | 322 | 暴力$dfs$,时间复杂度大概几个$log$?(不会分析.jpg) 323 | \end{solution} 324 | \lstinputlisting[language=C++, style=codestyle2]{code07/mostfactors.cpp} 325 | 326 | 327 | 328 | 329 | 330 | \section{FFT 与 NTT} 331 | \subsection{FFT} 332 | $FFT$在算法竞赛中的主要应用之一是加速多项式乘法的计算。 333 | 334 | \subsubsection{多项式} 335 | \begin{itemize} 336 | \item 多项式的系数表示: 337 | 338 | $$A(x)=\sum_{i=0}^{n-1}a_ix^i=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}$$ 339 | 340 | \item 多项式的点值表示: 341 | 342 | 对于上面的$n-1$次多项式,将一组(n个)互不相同的$x$带入进去得到对应的$y$(也就是n个点)。 343 | 这{\heiti n个点可以唯一确定一个多项式}。 344 | 345 | \begin{note} 346 | 由多项式的点值表示转系数表示,需要进行多项式插值,朴素的插值算法时间复杂度为$O(n^2)$。 347 | \end{note} 348 | \end{itemize} 349 | 350 | \subsubsection{多项式乘法} 351 | 有两个多项式,$A(x)=\sum_{i=0}^{n-1}a_ix^i$ 和$B(x)=\sum_{i=0}^{n-1}b_ix^i$ (假设两个多项式次数相同,若不同可在后面补零) 352 | 353 | 则 354 | $$ 355 | C(x)=A(x)*B(x)=\sum_{k=0}^{2n-2}(\sum_{i+j=k}a_ib_j)x^k 356 | $$ 357 | 两个$ n - 1$次多项式相乘,得到的是一个 $2n-2$ 次多项式,时间复杂度为 $O(n ^ 2)$。 358 | 359 | \begin{note} 360 | 另外,也可以用点值表达,即选择$2n-1$个互不相同的$x_i$带入$A(x)$和$B(x)$相乘,得到$2n-1$个 361 | 值,这$2n-1$个点值就唯一确定了这个多项式,时间复杂度$O(n)\ $({\heiti 注意只是得到点值表达式})。 362 | \end{note} 363 | 364 | \subsubsection{复数} 365 | 设a、b为实数,$i ^ 2=-1$ ,形如 $a + bi$的数叫做复数,其中$i$被称为虚数单位。复数域是已知最大的域。 366 | 367 | 368 | \subsubsection{复平面} 369 | 在复平面中,x 轴代表实数、y 轴(除原点外的所有点)代表虚数。每一个复数 $a + bi$ 对应复平面上一个从 (0, 0) 指向 (a, b) 的向量。 370 | 371 | 该向量的长度 $\sqrt {a ^ 2 + b ^ 2}$叫做{\heiti 模长}。 372 | 373 | 从 x 轴正半轴到该向量的转角的有向(以逆时针为正方向)角叫做{\heiti 幅角}。 374 | 375 | 复数相加遵循平行四边形定则。 376 | 377 | 复数相乘时,模长相乘,幅角相加。 378 | 379 | \subsubsection{单位根} 380 | 下文中,如不特殊指明,均取{\heiti n为2的正整数次幂}。 381 | 382 | 在复平面上,以原点为圆心,1为半径作圆,所得的圆叫做{\heiti 单位圆}。以原点为起点,单位圆的 n 等分点为终点,作 n个向量。设所得的{\heiti 幅角为正且最小}的向量对应 383 | 的复数为 $\omega_n$,称为 n 次单位根。 384 | 385 | 由复数乘法的定义(模长相乘,幅角相加)可知,其余的 $n - 1$个向量对应的复数分别为$\omega_n^2,\ \omega_n^3\ ,\ ...\ ,\ \omega_n^n$,其中 386 | $\omega_n ^ n = \omega_n ^ 0 = 1\ ,\ \omega_n^1=\omega_n$。 387 | 388 | 单位根的幅角为圆周角的 $1 \over n$,这为我们提供了一个计算单位根及其幂的公式: 389 | 390 | $$ 391 | \omega_n^k=cos(k\frac{2\pi}{n})+isin(k\frac{2\pi}{n}) 392 | $$ 393 | 394 | \subsubsection{单位根的性质} 395 | \begin{itemize} 396 | \item $\omega_{2n}^{2k}=\omega_n^k$ 397 | \begin{proof} 398 | 显然 399 | \end{proof} 400 | 401 | \vbox{} 402 | 403 | \item $\omega_n^{k+\frac{n}{2}}=-\omega_n^{k}$ 404 | \begin{proof} 405 | 因为$\omega_n^{\frac{n}{2}}=-1$(带入公式即可验证) 406 | \end{proof} 407 | \end{itemize} 408 | 409 | \subsubsection{离散傅里叶变换(DFT)} 410 | 411 | 对于一个$n$个系数,$n-1$次的多项式,考虑多项式$A(x)$的表示。 412 | 413 | 将n次{\heiti 单位根的0到$n-1$次幂}(共n个)带入多项式的系数表示,所得点值向量为: 414 | $$ 415 | \left \{ A(\omega_n^0)\ ,\ A(\omega_n^1)\ ,\ A(\omega_n^2)\ ,\ ...\ ,\ A(\omega_n^{n-1}) \right \} 416 | $$ 417 | \begin{note} 418 | 变换后的这n个点对,可以唯一确定这个多项式。 419 | \end{note} 420 | 421 | 称这个结果$(A(\omega_n^0)\ ,\ A(\omega_n^1)\ ,\ A(\omega_n^2)\ ,\ ...\ ,\ A(\omega_n^{n-1}))$为其系数向量$(a_0\ ,\ a_1\ ,\ a_2\ ,\ ...\ ,\ a_{n-1})$的{\heiti 离散傅里叶变换}。 422 | 423 | 按照朴素方法依次带入来求解原系数的离散傅里叶变换,时间复杂度仍为$O(n^2)$。 424 | 425 | 而这个过程可以{\heiti 分治进行},因而可以优化,即$FFT$,这是算法竞赛的重点。({\heiti 但此时还不知道这n个点对求多项式乘法有什么用,继续看}) 426 | 427 | 428 | \begin{definition}{从信号的角度看}{label} 429 | 如果从信号的角度看,这个过程就是{\heiti 时域到频域的转换}, 即选择一段时间取样$N$个点,即$0$时刻,$1$时刻,...,$N-1$时刻。转换的思路是这样的: 430 | 我去“枚举”一些频率,对于一个固定的频率$f$,去对{\heiti 时域图像(假设为$x(t)$)}做“缠绕操作”,而缠绕的时间取样就是上面的N个时刻, 431 | 即$X(f)=\sum_{n=0}^{N-1}x(n)w_N^{fn}$ (n是每个时间点)。简单来说,缠绕操作就是对一个{\heiti 无明显频率规律}的时域图像, 432 | 看它在指定(枚举的)频率上是否有这个频率,衡量的指标就是$X$ 。(至于缠绕操作,就是一种方便的工具,使得$x(n)$分布在特定的角 433 | 度上)。所以我们要枚举频率,一般也取$0\sim N-1$ 。 434 | 435 | 我们对$X(f)=\sum_{n=0}^{N-1}x(n)w_N^{fn}$ 简单变个形: 436 | $$ 437 | X(f)=\sum_{n=0}^{N-1}x(n)(w_N^{f})^n 438 | $$ 439 | 再看下朴素的多项式 440 | $$ 441 | A(x)=\sum_{i=0}^{n-1}a_ix^i=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1} 442 | $$ 443 | 即$f$分别取$0,1,2,...,n-1$,就是多项式$A(x)$带入$x=w_n^0\ ,\ w_n^1\ ,\ ...\ ,\ w_n^{n-1}$。 444 | 445 | 而多项式的系数$a_0\ ,\ a_1\ ,\ ...\ ,\ a_{n-1}$就对应$x(0)\ ,\ x(1)\ ,\ ...\ ,\ x(n-1)$ ({\heiti 即信号在时域离散时间点上的n个强度值})。 446 | 447 | \end{definition} 448 | 449 | \subsubsection{快速傅里叶变换(FFT)} 450 | 考虑将多项式按照系数下标的奇偶分为两部分 451 | $$ 452 | A(x)=(a_0+a_2x^2+a_4x^4+...+a_{n-2}x^{n-2})+(a_1x+a_3x^3+a_5x^5+...+a_{n-1}x^{n-1}) 453 | $$ 454 | 令 455 | \begin{align*} 456 | A_1(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{\frac{n}{2}-1} \\ 457 | A_2(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{\frac{n}{2}-1} 458 | \end{align*} 459 | 则有 460 | $$ 461 | A(x)=A_1(x^2)+xA_2(x^2) 462 | $$ 463 | 假设$k<\frac{n}{2}$,现在要求$A(\omega_n^k)$,则根据单位根的性质1,有: 464 | $$ 465 | A(\omega_n^k)=A_1(\omega_\frac{n}{2}^{k})+\omega_n^kA_2(\omega_\frac{n}{2}^{k}) 466 | $$ 467 | 由于前面$k<\frac{n}{2}$,对于$A(\omega_n^{k+\frac{n}{2}})$的部分,使用单位根的性质1和性质2,有:(这个指数范围的变化是精华) 468 | $$ 469 | A(\omega_n^{k+\frac{n}{2}})=A(-w_n^k)=A_1(\omega_\frac{n}{2}^{k})-\omega_n^kA_2(\omega_\frac{n}{2}^{k}) 470 | $$ 471 | 这样,当$ k$ 取遍 $[0,\ \frac{n}{2}-1]$ 时,$k$ 和 $k + \frac{n}{2}$取遍了 $[0,\ n-1]$,即全部所求。 472 | 473 | 那么,如果已知$A_1(x)$和$A_2(x)$在$\omega_\frac{n}{2}^0\ ,\ \omega_\frac{n}{2}^1\ ,\ ...\ ,\ \omega_\frac{n}{2}^{\frac{n}{2}-1}$的值, 474 | 就可以在$O(n)$时间内求得$A(x)$在$\omega_n^0\ ,\ \omega_n^1\ ,\ \omega_n^2\ ,\ ...\ ,\ \omega_n^{n-1}$ 处的取值。而{\heiti 关于$A_1(x),A_2(x)$的问题正好是原问题规模缩小一半的子问题},分治的边界为一个常数项$a_0$,即$A_\phi(x)$的系数只有一项。 475 | 476 | 则该分治算法的时间复杂度为 477 | $$ 478 | T(n)=2*T(n/2)+O(n)=O(nlogn) 479 | $$ 480 | 上述过程称为{\heiti $Cooley-Tukey$ 算法(JW Cooley 和 John Tukey)}。 481 | 482 | {\heiti 现在我们可以在$O(nlogn)$时间内求得n个点对啦!} 483 | 484 | 即$(\omega_n^0\ ,\ \omega_n^1\ ,\ \omega_n^2\ ,\ ...\ ,\ \omega_n^{n-1})$和$(A(\omega_n^0)\ ,\ A(\omega_n^1)\ ,\ A(\omega_n^2)\ ,\ ...\ ,\ A(\omega_n^{n-1}))$。 485 | 486 | 然而还是不知道和多项式乘法有什么关系...... 487 | 488 | 事实上,这n对取值,将作为“原料”,帮助我们{\heiti 反过来去求多项式的系数}。 489 | 490 | 和一般的插值不同($O(n^2)$),{\heiti 这些特殊的点对可以在$O(nlogn)$内求出多项式的系数}。 491 | 492 | 即下面的傅里叶逆变换,仿佛离最终目标进了一大步...... 493 | 494 | \subsubsection{傅里叶逆变换} 495 | 将点值表示的多项式转化为系数表示,同样可以使用快速傅里叶变换,这个过程叫做{\heiti 傅里叶逆变换}。 496 | 497 | 设$(y_0\ ,\ y_1\ ,\ y_2\ ,\ ...\ ,\ y_{n-1})$ 为$(a_0\ ,\ a_1\ ,\ a_2\ ,\ ...\ ,\ a_{n-1})$的离散傅里叶变换。考虑另一个向量$(C_0\ ,\ C_1\ ,\ C_2\ ,\ ...\ ,\ C_{n-1})$,满足 498 | $$ 499 | C_k=\sum_{i=0}^{n-1}y_i(\omega_n^{-k})^i \quad,k\in [0,n-1] 500 | $$ 501 | 即多项式$B(x)=y_0+y_1x+y_2x^2+...+y_{n-1}x^{n-1}$ 在$\omega_n^0\ ,\ \omega_n^{-1}\ ,\ \omega_n^{-2}\ ,\ ...\ ,\ \omega_n^{-(n-1)}$ 处的点值表示。 502 | 其中{\heiti $B(x)$以原系数$a_i$的离散傅里叶变换作为新的系数。} 503 | 504 | 将$C_k$展开: 505 | \begin{align*} 506 | C_k=\sum_{i=0}^{n-1}y_i(\omega_n^{-k})^i \\ 507 | =\sum_{i=0}^{n-1} ( \sum_{j=0}^{n-1}a_j(\omega_n^i)^j ) (\omega_n^{-k})^i \\ 508 | =\sum_{i=0}^{n-1} ( \sum_{j=0}^{n-1}a_j(\omega_n^j)^i ) (\omega_n^{-k})^i \\ 509 | =\sum_{i=0}^{n-1} ( \sum_{j=0}^{n-1}a_j(\omega_n^j)^i (\omega_n^{-k})^i ) \\ 510 | =\sum_{i=0}^{n-1} ( \sum_{j=0}^{n-1}a_j(\omega_n^{j-k})^i ) \\ 511 | =\sum_{j=0}^{n-1} a_j(\sum_{i=0}^{n-1}(\omega_n^{j-k})^i ) \\ 512 | \end{align*} 513 | 观察内层求和,为了解决这个求和式,先考虑一个式子先。令首项为1,公比为$\omega_n^k$的等比数列前n项和为$S(\omega_n^k)$: 514 | $$ 515 | S(\omega_n^k)=1+\omega_n^k+(\omega_n^k)^2+...+(\omega_n^k)^{n-1} 516 | $$ 517 | \begin{itemize} 518 | \item 当$k\neq0$ 时,两边同时乘上$\omega_n^k$,得 519 | $$ 520 | \omega_n^kS(\omega_n^k)=\omega_n^k+(\omega_n^k)^2+(\omega_n^k)^3+...+(\omega_n^k)^{n} 521 | $$ 522 | 上面两式相减,整理后得 523 | \begin{align*} 524 | \omega_n^kS(\omega_n^k)-S(\omega_n^k)=(\omega_n^k)^{n}-1 \\ 525 | S(\omega_n^k)=\frac{(\omega_n^k)^{n}-1 }{\omega_n^k-1} 526 | \end{align*} 527 | 这个式子分子为0,分母一定不为0,因此 528 | $$ 529 | S(\omega_{n}^{k})=0 530 | $$ 531 | 532 | \item 当$k=0$时,$S(\omega_{n}^{k})=n$ 533 | \end{itemize} 534 | 535 | \vbox{} 536 | 537 | 继续考虑$C_k$, 538 | \begin{align*} 539 | C_k=\sum_{j=0}^{n-1} a_j(\sum_{i=0}^{n-1}(\omega_n^{j-k})^i ) \\ 540 | =\sum_{j=0}^{n-1} a_jS(\omega_{n}^{j-k}) 541 | \end{align*} 542 | 对每一个$k$,枚举$j$,只有当$j=k$时,$S(\omega_{n}^{j-k})=n$,否则$S(\omega_{n}^{j-k})=0$,即 543 | \begin{align*} 544 | C_i=na_i \\ 545 | a_i=\frac{1}{n}C_i 546 | \end{align*} 547 | 548 | 也就是说,{\heiti 使用原系数的离散傅里叶变换结果作为新的系数,单位根的倒数代替单位根代入,做一次快速傅里叶变换的过程,再将结果每个数除以$n$},即为傅里叶逆变换的结果,即原系数$a_i$。 549 | 550 | 这样就可以先做两次正变换再做一次逆变换求得系数啦。 551 | 552 | 简单来说,求解多项式乘法的大致思路就是: 553 | 554 | \begin{enumerate} 555 | \item 确定结果多项式$C(x)$的次数,决定要取的点对的数量$N$($N$为2的次幂); 556 | \item $O(nlogn)$求$A(x)$和$B(x)$在这$N$个点的值,然后依次相乘得到结果多项式在这$N$个点的值; 557 | \item 做傅里叶逆变换求得系数值,即为结果多项式$C(x)$的系数。 558 | \end{enumerate} 559 | 560 | \subsubsection{小结与实现细节} 561 | $DFT$(离散傅里叶变换)是一种对$n$个元素的数组的变换,直接代入依次求解的方法是$O(n^2)$的。但是用分治的方法是$O(nlogn)$,即$FFT$(快速傅里叶变换)。 562 | 563 | 由于DFT满足{\heiti cyclic convolution(循环卷积)}的性质,即定义$h=a (*) b$ 为$h_r=\sum_{x+y=r}a_xb_y\ $(多项式乘法,$h$为结果多项式), 564 | 则有$DFT(a (*) b)=DFT(a)\cdot DFT(b)$,右边为向量点乘。 565 | 566 | 所以所求$a (*) b=DFT^{-1}(DFT(a)\cdot DFT(b))$, 567 | 即只要对$a,b$向量分别进行$DFT$,所得的两个向量点乘之后再逆变换就可以了。 568 | 569 | \begin{note} 570 | \begin{itemize} 571 | \item 由于$FFT$本身算法的要求,n是2的次幂,注意补0; 572 | \item DFT是定义在复数域上的,有与整数之间变换的要求; 573 | \item 不要忘记最后除n; 574 | \item C++ 的 $STL$ 在头文件'complex'中提供一个复数的模板实现'std::complex',其中T为实数类型,一般取'double',在对精度要求较高的时候可以使用'long double'或'\_\_float128'(不常用); 575 | \item 单位根的倒数等于其共轭复数,使用`std::conj()` 取得 $IDFT$ 所需的单位根的倒数; 576 | \item 对时间要求苛刻时,可以自己实现复数类以提高速度。 577 | \end{itemize} 578 | \end{note} 579 | 580 | 581 | \subsubsection{FFT的递归实现} 582 | 直接按照上述思路实现即可,使用C++已经封装的Complex。 583 | \href{http://uoj.ac/problem/34}{uoj-34 多项式乘法} 584 | 585 | \begin{figure}[!htbp] 586 | \centering 587 | \includegraphics[width=0.8\textwidth]{fft1.png} 588 | \caption{fft-recursion \label{fig:fft1}} 589 | \end{figure} 590 | 591 | \lstinputlisting[language=C++, style=codestyle2]{code07/fft-recursion.cpp} 592 | 593 | 594 | \subsubsection{FFT的迭代实现} 595 | 递归实现的 $FFT$ 效率不高,因为有栈的调用和参数的传递,实际中一般采用迭代实现。 596 | 597 | {\heiti 二进制位翻转} 598 | 599 | 对分治规律的总结: 600 | \begin{figure}[!htbp] 601 | \centering 602 | \includegraphics[width=1.0\textwidth]{fft-iter.png} 603 | \caption{分治的规律 \label{fig:fft-pattern}} 604 | \end{figure} 605 | 这为迭代实现提供了理论基础。 606 | 607 | {\heiti 蝴蝶操作} 608 | 609 | 考虑合并两个子问题的过程,假设$A_1(w_{\frac{n}{2}}^k)$ 和$A_2(w_{\frac{n}{2}}^k)$ 分别存放在$a[k]$和$a[\frac{n}{2}+k]$中,$A(w_n^k)$和$A(w_n^{k+\frac{n}{2}})$ 将要存放在$b[k]$和$b[\frac{n}{2}+k]$中,合并的操作可以表示为: 610 | \begin{align*} 611 | b[k]\leftarrow a[k]+w_n^k*a[\frac{n}{2}+k] \\ 612 | b[k+\frac{n}{2}] \leftarrow a[k]-w_n^k*a[\frac{n}{2}+k] 613 | \end{align*} 614 | 考虑加入一个临时变量$t$,使得这个过程可以在原地完成,而不需要数组$b$, 615 | \begin{align*} 616 | t \leftarrow w_n^k*a[\frac{n}{2}+k] \\ 617 | a[k+\frac{n}{2}] \leftarrow a[k]-t \\ 618 | a[k] \leftarrow a[k]+t 619 | \end{align*} 620 | 由于$k$和$k+\frac{n}{2}$ 是对应的,所以不同的$k$之间不会相互影响。 621 | 622 | 这一过程被称为蝴蝶操作。 623 | 624 | \begin{figure}[!htbp] 625 | \centering 626 | \includegraphics[width=0.8\textwidth]{fft2.png} 627 | \caption{fft-iteration \label{fig:fft2}} 628 | \end{figure} 629 | 630 | \lstinputlisting[language=C++, style=codestyle2]{code07/fft-iteration.cpp} 631 | 632 | \subsection{NTT} 633 | 由于$FFT$涉及到复数运算,难免有精度问题,在计算一些高精度乘法的时候就有可能由于精度出现错误,这 634 | 便让我们考虑是否有在模意义下的方法,这就是{\heiti 快速数论变换}($Fast\ Number-Theoretic\ Transform,\ NTT$)。 635 | 636 | 首先来看$FFT$中能归并地变换用到了单位根$\omega_n$的什么性质: 637 | \begin{enumerate} 638 | \item $\omega_n^n = 1$; 639 | \item $\omega_n^0\ ,\ \omega_n^1\ ,\ \omega_n^2\ ,\ ...\ ,\ \omega_n^{n-1}$是互不相同的,这样代入计算出来的点值才可以用来还原出多项式的系数; 640 | \item $\omega_n^{k+\frac{n}{2}}=-\omega_n^{k}$,$\ \omega_{2n}^{2k}=\omega_n^k$,这使得问题规模得以减半; 641 | \item $\sum_{i=0}^{n-1}(\omega_n^{j-k})^i = \left\{\begin{matrix} 642 | 0,\quad j\neq k\\ 643 | n,\quad j= k 644 | \end{matrix}\right.$,这使得可以用同样的优化方法加速逆变换。 645 | \end{enumerate} 646 | 647 | 下面我们要在模意义下寻找满足这些性质的数! 648 | 649 | \subsubsection{原根} 650 | 由第三章中知一个素数$p$的原根$g$满足$g^0\ ,\ g^1\ ,\ ...\ ,\ g^{p-2}\ (mod\ p)$均不相同。对于一个素数$p$,将其 651 | 写成$p=k*2^m + 1$的形式,记$2^m=n$,有$g^{kn}\equiv 1\ (mod\ p)$。我们记$g_n = g^k$,则有$g_n^n\equiv 1\ (mod\ p)$。 652 | 由于$g$是$p$的原根,则$g_n^0\ ,\ g_n^1\ ,\ ...\ ,\ g_n^{n-1}$互不相同。 653 | 654 | 到这里我们发现,在模$p$意义下,$p$的原根$g$的$k$次方$g_n$,具有和单位根$\omega_n$一样的性质!上面已经说明了其具有性质1和性质2。下面说明性质3。 655 | 656 | 由于$g_n^n\equiv 1\ (mod\ p)$,所以$g_n^{\frac{n}{2}}\equiv 1\ or\ -1\ (mod\ p)$。又由于$g$是原根,则$g_n^{\frac{n}{2}}\equiv -1\ (mod\ p)$。 657 | 所以$g_n^{k+\frac{n}{2}}\equiv -g_n^{k}$得证。至于$g_{2n}^{2k}\equiv g_n^k$,只需证$g_n^2\equiv g_{\frac{n}{2}}$。这是比较显然的,因为$k=\frac{p-1}{2^m}$。 658 | 659 | 对于性质4,证明方法类似,大家可以自行尝试。 660 | 661 | 因此在$NTT$中,我们可以用原根$g$的$k$次方$g_n$代替$FFT$中的单位根$\omega_{n}$。 662 | 663 | \subsubsection{NTT的实现细节} 664 | 在$FFT$中,需要预处理$N$项单位根的次幂及其共轭,对应地,$NTT$中也需要。即预处理$g_n^0\ ,\ g_n^1\ ,\ ...\ ,\ g_n^{n-1}$,以及对应的模$p$意义下的逆元。 665 | 666 | $NTT$的代码相比$FFT$的代码变动很少。 667 | \begin{note} 668 | \begin{enumerate} 669 | \item 最后逆变换完成后除$n$时,变为乘上$n$关于$p$的逆元即可; 670 | \item $p=k*2^m + 1$中,最大能取到的$2^m$为能处理的结果多项式项数极限。如对于素数$998244353= 119 * 2^{23} + 1$, 最多能处理的$N = 2^{23}$。当$N<2^{23}$时,要将 671 | 多余的部分乘到$k$上;(常见的素数及其原根见附录表) 672 | \item 由于实数域上的运算比复数域运算要快,所以$NTT$的运行时间理论上会比$FFT$少;(但由于有取模操作在,所以上述说明是玄学......) 673 | \item 如果一个题目不用取模,使用$NTT$时要注意原本的答案是否会大于等于所选取的模数$p$。 674 | \end{enumerate} 675 | \end{note} 676 | 677 | \begin{figure}[!htbp] 678 | \centering 679 | \includegraphics[width=0.8\textwidth]{ntt.png} 680 | \caption{NTT-iteration \label{fig:ntt}} 681 | \end{figure} 682 | 683 | \lstinputlisting[language=C++, style=codestyle2]{code07/NTT.cpp} 684 | 685 | \subsubsection{模数任意的解决方案} 686 | $NTT$要求模数写成$p=k*2^m + 1$的形式,但有些题目,要求模数写不成这样的形式,甚至是一个合数。 687 | 这样的话,一般选取$s$个常用的素数$p_1\ ,\ p_2\ ,\ ...\ ,\ p_s$,要求满足: 688 | $$ 689 | \prod_{i=1}^s p_i > n(m-1)^2 690 | $$ 691 | 其中$n$是结果多项式的项数,$m$是要求的模数。然后分别在$mod\ p_i$下求解系数,最后使用中国剩余定理将结果合并(一般会使用高精度)。 692 | 693 | 694 | %\section{多项式} 695 | 696 | %\subsection{拉格朗日插值法} 697 | 698 | %\subsection{多项式求逆} 699 | 700 | %\subsection{多项式取模} 701 | 702 | %\subsection{多项式开方} 703 | 704 | %\subsection{多项式多点求值} 705 | 706 | %\subsection{多项式多点插值} 707 | 708 | \vbox{} 709 | 710 | \vbox{} 711 | 712 | \begin{problemset} 713 | \item \href{http://poj.org/problem?id=1305}{本原勾股数组 \quad poj 1305} 714 | \item 本原勾股数组 \quad fzu 1669 715 | \item \href{https://nanti.jisuanke.com/t/41421}{圆上整点/高斯素数 \quad 2019上海网络赛} 716 | \item \href{https://www.51nod.com/Challenge/Problem.html#problemId=1195}{斐波那契数列循环节 \quad 51nod-1195} 717 | \item dfs similar \quad 2019icpc亚洲区域赛-南昌站B题 718 | \item FFT \quad BZOJ 3992 / SDOI2015 序列统计 719 | \item FFT \quad BZOJ 3771 Triple 720 | \item \href{https://www.lydsy.com/JudgeOnline/problem.php?id=3527}{BZOJ 3527 \quad FFT} 721 | \item \href{https://www.luogu.com.cn/problem/P4245}{模板:任意模数NTT} 722 | \end{problemset} 723 | 724 | 725 | \nocite{*} 726 | 727 | \bibliography{reference} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yuxiang Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Number-Theory-in-Programming-Competition 2 | This is an introduction tutorial about number theory in Programming Competition, Code is Implemented in C++. 3 | 4 | you can download the pdf version in the directory above. 5 | -------------------------------------------------------------------------------- /app1.tex: -------------------------------------------------------------------------------- 1 | \chapter{常用的表} 2 | 3 | 4 | \section{梅森素数表} 5 | Mersenne exponents: primes $p$ such that $2^p - 1$ is prime. Then $2^p - 1$ is called a Mersenne prime. 6 | 见表\ref{tab:Mersenne-prime}。 7 | \begin{table}[!htbp] 8 | \centering 9 | \caption{梅森素数指数 \label{tab:Mersenne-prime}} 10 | \begin{tabular}{|c|c|c|c|} 11 | \hline 12 | n & a(n) & 24 & 19937 \\ \hline 13 | 1 & 2 & 25 & 21701 \\ \hline 14 | 2 & 3 & 26 & 23209 \\ \hline 15 | 3 & 5 & 27 & 44497 \\ \hline 16 | 4 & 7 & 28 & 86243 \\ \hline 17 | 5 & 13 & 29 & 110503 \\ \hline 18 | 6 & 17 & 30 & 132049 \\ \hline 19 | 7 & 19 & 31 & 216091 \\ \hline 20 | 8 & 31 & 32 & 756839 \\ \hline 21 | 9 & 61 & 33 & 859433 \\ \hline 22 | 10 & 89 & 34 & 1257787 \\ \hline 23 | 11 & 107 & 35 & 1398269 \\ \hline 24 | 12 & 127 & 36 & 2976221 \\ \hline 25 | 13 & 521 & 37 & 3021377 \\ \hline 26 | 14 & 607 & 38 & 6972593 \\ \hline 27 | 15 & 1279 & 39 & 13466917 \\ \hline 28 | 16 & 2203 & 40 & 20996011 \\ \hline 29 | 17 & 2281 & 41 & 24036583 \\ \hline 30 | 18 & 3217 & 42 & 25964951 \\ \hline 31 | 19 & 4253 & 43 & 30402457 \\ \hline 32 | 20 & 4423 & 44 & 32582657 \\ \hline 33 | 21 & 9689 & 45 & 37156667 \\ \hline 34 | 22 & 9941 & 46 & 42643801 \\ \hline 35 | 23 & 11213 & 47 & 43112609 \\ \hline 36 | \end{tabular} 37 | \end{table} 38 | 39 | 40 | \section{卡米歇尔数} 41 | 见表\ref{tab:Carmichael-numbers}。 42 | \begin{table}[!htbp] 43 | \centering 44 | \caption{卡米歇尔数前面一点 \label{tab:Carmichael-numbers}} 45 | \begin{tabular}{|c|c|} 46 | \hline 47 | a & a(n) \\ \hline 48 | 1 & 561 \\ \hline 49 | 2 & 1105 \\ \hline 50 | 3 & 1729 \\ \hline 51 | 4 & 2465 \\ \hline 52 | 5 & 2821 \\ \hline 53 | 6 & 6601 \\ \hline 54 | 7 & 8911 \\ \hline 55 | 8 & 10585 \\ \hline 56 | 9 & 15841 \\ \hline 57 | 10 & 29341 \\ \hline 58 | 11 & 41041 \\ \hline 59 | 12 & 46657 \\ \hline 60 | 13 & 52633 \\ \hline 61 | 14 & 62745 \\ \hline 62 | 15 & 63973 \\ \hline 63 | 16 & 75361 \\ \hline 64 | 17 & 101101 \\ \hline 65 | 18 & 115921 \\ \hline 66 | 19 & 126217 \\ \hline 67 | 20 & 162401 \\ \hline 68 | 21 & 172081 \\ \hline 69 | 22 & 188461 \\ \hline 70 | 23 & 252601 \\ \hline 71 | 24 & 278545 \\ \hline 72 | 25 & 294409 \\ \hline 73 | \end{tabular} 74 | \end{table} 75 | 76 | \section{常见的素数及其原根} 77 | 见表\ref{tab:ntt-primes}。 78 | \begin{table}[!htbp] 79 | \centering 80 | \caption{常见的素数及其原根 \label{tab:ntt-primes}} 81 | \begin{tabular}{|c|c|c|c|} 82 | \hline 83 | $p=k*2^m + 1$ & $k$ & $m$ & $groot$ \\ \hline 84 | 3 & 1 & 1 & 2 \\ \hline 85 | 5 & 1 & 2 & 2 \\ \hline 86 | 17 & 1 & 4 & 3 \\ \hline 87 | 97 & 3 & 5 & 5 \\ \hline 88 | 193 & 3 & 6 & 5 \\ \hline 89 | 257 & 1 & 8 & 3 \\ \hline 90 | 7681 & 15 & 9 & 17 \\ \hline 91 | 12289 & 3 & 12 & 11 \\ \hline 92 | 40961 & 5 & 13 & 3 \\ \hline 93 | 65537 & 1 & 16 & 3 \\ \hline 94 | 786433 & 3 & 18 & 10 \\ \hline 95 | 5767169 & 11 & 19 & 3 \\ \hline 96 | 7340033 & 7 & 20 & 3 \\ \hline 97 | 23068673 & 11 & 21 & 3 \\ \hline 98 | 104857601 & 25 & 22 & 3 \\ \hline 99 | 167772161 & 5 & 25 & 3 \\ \hline 100 | 469762049 & 7 & 26 & 3 \\ \hline 101 | { \color{red}998244353} & 119 & 23 & 3 \\ \hline 102 | {\color{red}1004535809} & 479 & 21 & 3 \\ \hline 103 | 2013265921 & 15 & 27 & 31 \\ \hline 104 | {\color{red}2281701377} & 17 & 27 & 3 \\ \hline 105 | 3221225473 & 3 & 30 & 5 \\ \hline 106 | 75161927681 & 35 & 31 & 3 \\ \hline 107 | 77309411329 & 9 & 33 & 7 \\ \hline 108 | 206158430209 & 3 & 36 & 22 \\ \hline 109 | 2061584302081 & 15 & 37 & 7 \\ \hline 110 | 2748779069441 & 5 & 39 & 3 \\ \hline 111 | 6597069766657 & 3 & 41 & 5 \\ \hline 112 | 39582418599937 & 9 & 42 & 5 \\ \hline 113 | 79164837199873 & 9 & 43 & 5 \\ \hline 114 | 263882790666241 & 15 & 44 & 7 \\ \hline 115 | 1231453023109121 & 35 & 45 & 3 \\ \hline 116 | 1337006139375617 & 19 & 46 & 3 \\ \hline 117 | 3799912185593857 & 27 & 47 & 5 \\ \hline 118 | 4222124650659841 & 15 & 48 & 19 \\ \hline 119 | 7881299347898369 & 7 & 50 & 6 \\ \hline 120 | 31525197391593473 & 7 & 52 & 3 \\ \hline 121 | 180143985094819841 & 5 & 55 & 6 \\ \hline 122 | 1945555039024054273 & 27 & 56 & 5 \\ \hline 123 | 4179340454199820289 & 29 & 57 & 3 \\ \hline 124 | \end{tabular} 125 | \end{table} 126 | 127 | \section{一些公式} 128 | \subsection{切比雪夫多项式} 129 | $$ 130 | \cos n x=\left\{\begin{array}{l}{\sum_{k=0}^{\frac{n}{2}}(-1)^{\frac{n-2 k}{2}} \frac{n \cdot(n+2 k-2) ! !}{(2 k) !(n-2 k) ! !} \cos ^{2 k} x} \ , \quad n\ is\ even \\ 131 | {\sum_{k=1}^{\frac{n+1}{2}}(-1)^{\frac{n+1-2 k}{2}} \frac{n \cdot(n+2 k-3) !}{(2 k-1) !(n+1-2 k) ! !} \cos ^{2 k-1} x} \ , \quad n\ is\ odd \end{array}\right. 132 | $$ 133 | 134 | 135 | \newpage 136 | 137 | 此页为草稿 -------------------------------------------------------------------------------- /code01/exgcd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 拓展欧几里得算法 (a,b非零) 3 | 求解ax+by=gcd(a,b)的一个解(x1,y1) 4 | 则通解为(x1+k*b/g,y1-k*a/g) 5 | 6 | 如果求解ax+by=c 7 | 输入a,b后 下面模板会求出 8 | ax+by=+\-gcd(a,b)的一个解(x1,y1) 9 | 正负号虽然不确定(和扩欧写法有关) 但等式是成立的 10 | ax+by=c 的一个解为(x2,y2)=c/g*(x1,y1) 11 | 则通解为(x2+k*b/g,y2-k*a/g) 12 | */ 13 | #include 14 | using namespace std; 15 | typedef long long ll; 16 | ll e_gcd(ll a,ll b,ll &x,ll &y) 17 | { 18 | if(b==0){ 19 | x=1; 20 | y=0; 21 | return a; 22 | } 23 | ll ans=e_gcd(b,a%b,x,y); 24 | ll temp=x; 25 | x=y; 26 | y=temp-a/b*y; 27 | return ans; 28 | } 29 | int main() 30 | { 31 | ll a,b,x,y; 32 | cin>>a>>b; 33 | ll gcd=e_gcd(a,b,x,y); 34 | cout<<"gcd:"<1){//一个合数n必定有小于等于sqrt{n} 的质因子 将其全部除尽后 若不为1 则为大于sqrt{n}的质因子 且仅有一个 19 | a[++tot]=now; 20 | b[tot]=1; 21 | } 22 | } 23 | int main() 24 | { 25 | int n; 26 | cin>>n; 27 | factor(n);//n>1 28 | for(int i=1;i<=tot;++i){ 29 | if(i==tot) cout< ans[maxn];//质因子 5 | vector bns[maxn];//对应指数 6 | //先预处理每个数有哪些质因数 7 | void init1(int n)//处理1000005个数本机跑了1s左右 8 | { 9 | for(int i=2;i<=n;++i){ 10 | if(ans[i].size()==0){//说明是素数 11 | for(int j=i;j<=n;j+=i){ 12 | ans[j].push_back(i); 13 | } 14 | } 15 | } 16 | } 17 | void init2(int n) 18 | { 19 | for(int i=2;i<=n;++i){ 20 | int tp=i; 21 | for(int j=0;j>n; 35 | init1(n); 36 | init2(n); 37 | } -------------------------------------------------------------------------------- /code02/intereuler.cpp: -------------------------------------------------------------------------------- 1 | const int maxn=1e6+10; 2 | vector ans[maxn]; 3 | vector bns[maxn]; 4 | int min_prime[maxn]; 5 | int tot; 6 | int prime[maxn]; 7 | void init1(int n)//预处理每个数最小的质因子 8 | { 9 | tot=0; 10 | for(int i=2;i<=n;++i){ 11 | if(min_prime[i]==0){ 12 | prime[++tot]=i; 13 | min_prime[i]=i; 14 | } 15 | for(int j=1;j<=tot && prime[j]*i<=n;++j){ 16 | min_prime[prime[j]*i]=prime[j]; 17 | if(i%prime[j]==0) break; 18 | } 19 | } 20 | } 21 | void init2(int n) 22 | { 23 | for(int i=2;i<=n;++i){ 24 | int tp=i; 25 | while(tp!=1) 26 | { 27 | int num=0; 28 | int tem=min_prime[tp]; 29 | ans[i].push_back(tem); 30 | while(tp%tem==0) 31 | { 32 | num++; 33 | tp/=tem; 34 | } 35 | bns[i].push_back(num); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /code02/large-inter-factor.cpp: -------------------------------------------------------------------------------- 1 | const int maxn=(1<<16)+10; 2 | bool valid[maxn]; 3 | int prime[maxn]; 4 | int tot; 5 | void get_prime(int n) 6 | { 7 | tot=0; 8 | for(int i=2;i<=n;++i) valid[i]=true; 9 | for(int i=2;i<=n;++i){ 10 | if(valid[i]==true){ 11 | prime[++tot]=i; 12 | } 13 | for(int j=1;j<=tot && prime[j]*i<=n;++j){ 14 | valid[prime[j]*i]=false; 15 | if(i%prime[j]==0) break; 16 | } 17 | } 18 | } 19 | //<<2是为了1e6区间长度 20 | vector ans[maxn<<2];//区间每个数的质因子 0 对应 L 21 | vector bns[maxn<<2];//质因子对应的指数 22 | int main() 23 | { 24 | get_prime(1<<16); 25 | int L,R; 26 | cin>>L>>R; 27 | for(int i=1;i<=tot;++i){ 28 | for(int j=(L-1)/prime[i]+1;j<=R/prime[i];++j){ 29 | ans[j*prime[i]-L].push_back(prime[i]); 30 | } 31 | } 32 | //L存在0这里 33 | for(int i=0;i>L>>R) 29 | { 30 | for(int i=1;i<=(R-L+1);++i){vis[i]=true;}//-L+1编号 即L-> 1, L+1 ->2,... 31 | if(L==1) vis[1]=false;//1不是素数 32 | //下面用素数筛 33 | for(int i=1;i<=tot;i++)//tot 是预处理的所有素数的个数 34 | for(ll j=max(ans[i],(L-1)/ans[i]+1);j<=R/ans[i];j++)//当前素数的j倍 35 | vis[ans[i]*j-L+1]=false;//ans[i]*j是合数 36 | //(L-1)/ans[i]+1)找的是从L开始第一个ans[i]的倍数是其多少倍 37 | //考虑这里为什么当L较小时 从ans[i]倍ans[i]开始筛 (当L较大时自然从(L-1)/ans[i]+1倍开始) 38 | //能从ans[i]*ans[i]开始的条件是 ans[i]的ans[i-1]倍、ans[i-2]倍...已经筛过了(如果在L,R区间里) 39 | //那当i=i-1、i-2...时不就是吗 和埃筛一样 40 | vector tep;//答案 41 | for(int i=1;i<=(R-L+1);++i){ 42 | if(vis[i]) tep.push_back(L+i-1); 43 | } 44 | cout< 6 | using namespace std; 7 | typedef long long ll; 8 | ll fast_exp(ll a,ll b,ll c){ 9 | ll res=1;a=a%c;assert(b>=0); 10 | while(b>0) 11 | { 12 | if(b&1) res=(a*res)%c; 13 | b=b>>1;a=(a*a)%c; 14 | } 15 | return res; 16 | } 17 | ll gcd(ll a,ll b){if(b==0) return a;return gcd(b,a%b);} 18 | struct pli{ 19 | ll first; 20 | int second; 21 | pli(){} 22 | pli (ll x_,int y_){ 23 | first=x_; 24 | second=y_; 25 | } 26 | bool operator < (const pli &b) const { 27 | if(first==b.first) return second>b.second;//因为second更大的解更小,所以> 28 | return first hash; 46 | // for(int i=0;i!=M;++i){ 47 | // hash[base]=i;//存的是大的编号,所以可以保证最小解 48 | // base=base*a%m; 49 | // } 50 | // base=fast_exp(a,M,m);//必要时要用快速乘 51 | // ll now=t; 52 | // for(int i=1;i<=M+1;++i){ 53 | // now=now*base%m;//这里乘在左边了 相当于右边乘逆元 54 | // if(hash.count(now)) return i*M-hash[now]+cnt; 55 | // } 56 | pli hash[int(1e5)];//注意再大可能会爆内存 57 | for(int i=0;i!=M;++i){ 58 | hash[i]=pli(base,i); 59 | base=base*a%m; 60 | } 61 | sort(hash,hash+M);//默认以first 62 | base=fast_exp(a,M,m); 63 | ll now=t; 64 | for(int i=1;i<=M+1;++i){ 65 | now=now*base%m; 66 | //注意下面M+10 这样可以保证解最小 67 | int id=lower_bound(hash, hash+M, pli(now,M+10))-hash;//默认是first 68 | assert(id>=0 &&id<=M); 69 | if(id!=M && hash[id].first==now) return i*M-hash[id].second+cnt;//减去的编号second越大越好 70 | } 71 | return -1; 72 | } 73 | int main() 74 | { 75 | ll a,b,m; 76 | while(cin>>a>>m>>b,m) 77 | { 78 | if(m==1) assert(b==0); 79 | ll ans=bsgs(a,b,m); 80 | if(ans==-1) cout<<"No Solution"<1 36 | } 37 | return true; 38 | } 39 | vector carmichael; 40 | int main() 41 | { 42 | int tot; 43 | getprime(1e7,tot); 44 | for(int i=2;i<=1e7;++i) if(check(i)) carmichael.push_back(i); 45 | for(auto k:carmichael) cout<>n; 40 | for(int i=0;i>a[i]>>m[i]; 42 | cout<<"一个解为:"<>n; 47 | for(int i=0;i>m[i]>>a[i]; 48 | ll ans = EXCRT(a,m, n); 49 | if(crt_flag) cout<<"no solution"<=0); 5 | while(b>0) 6 | { 7 | if(b&1) res=(a*res)%c; 8 | b=b>>1; 9 | a=(a*a)%c; 10 | } 11 | return res; 12 | } -------------------------------------------------------------------------------- /code03/fastmul.cpp: -------------------------------------------------------------------------------- 1 | ll mod_mul(ll a,ll b,ll c){//a*b %c 乘法改加法 防止超long long 2 | ll res=0; 3 | a=a%c; 4 | assert(b>=0); 5 | while(b) 6 | { 7 | if(b&1) res=(res+a)%c; 8 | b>>=1; 9 | a=(a+a)%c; 10 | } 11 | return res; 12 | } -------------------------------------------------------------------------------- /code03/inverse.cpp: -------------------------------------------------------------------------------- 1 | //拓展欧几里得 2 | ll e_gcd(ll a,ll b,ll &x,ll &y) 3 | { 4 | if(b==0){x=1;y=0;return a;} 5 | ll ans=e_gcd(b,a%b,x,y); 6 | ll temp=x; x=y; y=temp-a/b*y; 7 | return ans; 8 | } 9 | 10 | //求逆元 已知gcd(a,mod)=1 11 | ll inv(ll a,ll mod) 12 | { 13 | ll ans,tmp; 14 | e_gcd(a,mod,ans,tmp); 15 | return (ans+mod)%mod; 16 | } 17 | -------------------------------------------------------------------------------- /code03/linearinverse.cpp: -------------------------------------------------------------------------------- 1 | typedef long long ll; 2 | const int mod=10007; 3 | ll inv[mod+10]; 4 | ll Fac[mod+10]; 5 | ll Fac_inv[mod+10]; 6 | void init() 7 | { 8 | inv[1]=1; 9 | Fac[0]=1; 10 | Fac_inv[0]=1; 11 | for(int i=1;i 2 | using namespace std; 3 | typedef __int128 lll; 4 | typedef long long ll; 5 | ll f(lll x, ll c, ll n){ return (x*x+c)%n; } 6 | ll gcd(ll a, ll b){ 7 | if(b==0) return a; 8 | return gcd(b, a%b); 9 | } 10 | ll mod_mul(lll a, ll b, ll c){return a*b%c;} 11 | ll fast_exp(ll a,ll b,ll c){ 12 | ll res=1;a=a%c; 13 | while(b) 14 | { 15 | if(b&1) res=mod_mul(res,a,c); 16 | b>>=1; a=mod_mul(a,a,c); 17 | } 18 | return res; 19 | } 20 | bool test(ll n,ll a)//false表示为合数 21 | { 22 | ll d = n-1; 23 | if(!(n&1)) return false; 24 | while(!(d&1)) d>>=1;//将d分解为奇数 至少有一个2因子,所以d!=n-1 25 | ll t=fast_exp(a,d,n); 26 | while(d!=n-1 && t!=1 && t!=n-1){ 27 | t=mod_mul(t,t,n);//平方 使用快速乘 因为t,n 1e18 或者int128 28 | d<<=1; 29 | } 30 | return ((t==n-1) || (d&1) ==1 );//两个条件都不成立则一定是合数; 第二个条件d为奇数 即t一开始为1 (mod n) 31 | //若两个有一个成立,且多次,对于合数来说,可能性极小,所以可以认为是素数 32 | } 33 | bool is_prime(ll n){ 34 | if(n<2) return false; 35 | if(n==2) return true; 36 | srand(time(0)); 37 | int test_num = 10; 38 | for(int i=0;i1) return d; 59 | } 60 | } 61 | ll d = gcd(val, N); 62 | if(d>1) return d; 63 | } 64 | } 65 | set factors;//所有质因子 66 | //ll max_factor;//最大质因子 67 | void solve(ll x) 68 | { 69 | if(x==1) return ; 70 | //if(x<=max_factor) return ; 发现这样剪枝并不快多少 2333 71 | if(is_prime(x)){ 72 | //max_factor = max(max_factor, x); 73 | factors.insert(x); 74 | return ; 75 | } 76 | ll y = pollard_rho(x); 77 | while(y>=x) y = pollard_rho(x); 78 | solve(y),solve(x/y); 79 | } 80 | int main(){ 81 | int t; 82 | cin>>t; 83 | while(t--) 84 | { 85 | //max_factor = 0; 86 | factors.clear(); 87 | ll n; 88 | cin>>n; 89 | if(is_prime(n)){cout<<"Prime"<1) //cout<>=1; 9 | // a=(a+a)%c; 10 | // } 11 | // return res; 12 | //} 13 | ll mod_mul(ll a, ll b, ll c){//或者int128 14 | return a*b%c; 15 | } 16 | ll fast_exp(ll a,ll b,ll c){ 17 | ll res=1; 18 | a=a%c; 19 | while(b) 20 | { 21 | if(b&1) res=mod_mul(res,a,c); 22 | b>>=1; 23 | a=mod_mul(a,a,c); 24 | } 25 | return res; 26 | } 27 | bool test(ll n,ll a)//false表示为合数 28 | { 29 | ll d = n-1; 30 | if(!(n&1)) return false; 31 | while(!(d&1)) d>>=1;//将d分解为奇数 至少有一个2因子,所以d!=n-1 32 | ll t=fast_exp(a,d,n); 33 | while(d!=n-1 && t!=1 && t!=n-1){ 34 | t=mod_mul(t,t,n);//平方 使用快速乘 因为t,n 1e18 或者int128 35 | d<<=1; 36 | } 37 | return ((t==n-1) || (d&1) ==1 );//两个条件都不成立则一定是合数; 第二个条件d为奇数 即t一开始为1 (mod n) 38 | //若两个有一个成立,且多次,对于合数来说,可能性极小,所以可以认为是素数 39 | } 40 | bool is_prime(ll n){ 41 | if(n<2) return false; 42 | if(n==2) return true; 43 | srand(time(0)); 44 | int test_num = 10; 45 | for(int i=0;i 2 | using namespace std; 3 | typedef long long ll; 4 | //拓展欧几里得模板 5 | ll e_gcd(ll a,ll b,ll &x,ll &y) 6 | { 7 | if(b==0){ 8 | x=1; 9 | y=0; 10 | return a; 11 | } 12 | ll ans=e_gcd(b,a%b,x,y); 13 | ll temp=x; 14 | x=y; 15 | y=temp-a/b*y; 16 | return ans; 17 | } 18 | //ax同余c(mod m) 输出[0,m) 中的解 19 | vector mod_equation(ll a,ll c,ll m){ 20 | ll u,v; 21 | ll d=e_gcd(a,m,u,v); 22 | vectorans; 23 | ans.clear(); 24 | if(c%d==0){ 25 | u=(u*(c/d)); 26 | u=(u%(m/d) + (m/d))%(m/d);//最小正整数解 27 | ans.push_back(u); 28 | for(ll k=1;kans; 34 | int main() 35 | { 36 | ll a,c,mod; 37 | cin>>a>>c>>mod; 38 | ans=mod_equation(a,c,mod); 39 | if(!ans.size()) cout<<"无解"< 2 | using namespace std; 3 | typedef __int128 lll; 4 | typedef long long ll; 5 | ll f(lll x, ll c, ll n){ return (x*x+c)%n; } 6 | ll gcd(ll a, ll b){ 7 | if(b==0) return a; 8 | return gcd(b, a%b); 9 | } 10 | ll pollard_rho(ll N){ 11 | if(N<=1) return -1; 12 | ll s=0,t=0,c=1LL*rand()*rand()%(N-1)+1; 13 | int goal, stp; 14 | lll val = 1; 15 | int up = (1<<6)-1; 16 | for(goal = 1;;goal<<=1,s=t,val=1)//维护区间 17 | { 18 | for(stp=1;stp<=goal;++stp){ 19 | t = f(t,c,N); 20 | val = val * abs(t-s) % N;//int 128 或者快读乘 21 | if(!(stp&up)) 22 | { 23 | ll d = gcd(val, N); 24 | if(d>1) return d; 25 | } 26 | } 27 | ll d = gcd(val, N); 28 | if(d>1) return d; 29 | } 30 | } 31 | int main(){ 32 | cout<1) return d; 13 | t=f(t,c,N),r=f(f(r,c,N),c,N); 14 | } 15 | return N;//没有找到,重新调整参数c 16 | } -------------------------------------------------------------------------------- /code03/primitive-root.cpp: -------------------------------------------------------------------------------- 1 | //求一个素数的原根 2 | #include 3 | using namespace std; 4 | typedef long long ll; 5 | vector a;//p-1的所有质因子 6 | ll fast_pow(ll a,ll b,ll c){ 7 | ll res=1; 8 | a=a%c; 9 | while(b) 10 | { 11 | if(b&1) res=(res*a)%c; 12 | b=b>>1; 13 | a=(a*a)%c; 14 | } 15 | return res; 16 | } 17 | bool g_test(ll g,ll p){ 18 | for(ll i=0;i1) res-=res/x; 15 | return res; 16 | } 17 | ll fast_exp(ll a,ll b,ll c){ 18 | ll res=1; 19 | a=a%c; 20 | assert(b>=0); 21 | while(b>0) 22 | { 23 | if(b&1) res=a*res%c; 24 | b=b>>1; 25 | a=a*a%c; 26 | } 27 | return res; 28 | } 29 | ll solve(ll a, ll b, ll m) 30 | { 31 | if(b==1) return a%m; 32 | if(m==1) return 0; 33 | ll phim = phi(m); 34 | //cout<<"phim: "<>t; 54 | while(t--) 55 | { 56 | ll a,b,m; 57 | cin>>a>>b>>m; 58 | if(a==1 || b==0){ 59 | cout<<1%m<=0); 22 | while(b>0) 23 | { 24 | if(b&1) res=mod_mul(res,a,c); 25 | b=b>>1; 26 | a=mod_mul(a, a, c); 27 | } 28 | return res; 29 | } 30 | int main(void) 31 | { 32 | //freopen("in.txt","r",stdin); 33 | int t; 34 | cin>>t; 35 | ll n,c; 36 | ll zhi = (1<<30)+3; 37 | for(int tcase=1;tcase<=t;++tcase){ 38 | cout<<"Case "<>n>>c; 40 | ll p,q; 41 | ll M = sqrt(n)+1e1; 42 | while(1) 43 | { 44 | if(n%M==0){ 45 | p = M; 46 | q = n / M; 47 | break; 48 | } 49 | M--; 50 | } 51 | ll ni = inv(zhi, (p-1)*(q-1)); 52 | ll ans = fast_exp(c, ni, n); 53 | cout<2 3 | typedef long long ll; 4 | ll a,p;//求解勒让德符号(a/p) 5 | ll Legendre(ll a,ll p) 6 | { 7 | a=a%p; 8 | if(a<0) a+=p; 9 | int num=0;//将a的2次幂提出 保证是奇数 10 | while(a%2==0){ 11 | a>>=1; 12 | num++; 13 | } 14 | ll ans1,ans2=1;//ans2是2的次幂的答案,默认为1 15 | if(a==1) ans1=1; 16 | else if(a==2){ 17 | if(p%8==1||p%8==7) ans1=1; 18 | else ans1=-1; 19 | } 20 | else{ 21 | if(p%4==3 && a%4==3) ans1=-Legendre(p,a); 22 | else ans1=Legendre(p,a); 23 | } 24 | if(num%2 &&(p%8==3||p%8==5)) ans2=-1; 25 | return ans1*ans2; 26 | } 27 | int main() 28 | { 29 | int t; 30 | cin>>t; 31 | int T=0; 32 | while(t--) 33 | { 34 | T++; 35 | cin>>a>>p; 36 | cout<<"Scenario #"<'9')||(c<'0'))c=getchar(); 13 | while((c>='0')&&(c<='9'))ret=(ret<<1)+(ret<<3)+c-'0',c=getchar(); 14 | return ret; 15 | } 16 | inline int Pow(int x,int k){ 17 | int ans=1; 18 | for(;k;k>>=1,x=1LL*x*x%mo)if(k&1)ans=1LL*ans*x%mo; 19 | return ans; 20 | } 21 | inline int exgcd(int a,int b,int &x,int &y){ 22 | if(a==0){y=1;x=0;return b;} 23 | int tmp=b/a,d=exgcd(b%a,a,y,x); 24 | x-=tmp*y; 25 | return d; 26 | } 27 | inline int find(int x){ 28 | for(int i=2;;i++)if(i%x){ 29 | bool ok=true; 30 | for(int j=1;j<=cnt;j++)if(Pow(i,phi/p[j])==1){ok=false;break;} 31 | if(ok)return i; 32 | } 33 | } 34 | void ins(int k,int x){ 35 | int key=x%Mo; 36 | h[++hcnt]=(hmap){x,k,head[key]}; 37 | head[key]=hcnt; 38 | } 39 | inline int query(int x){ 40 | int key=x%Mo; 41 | for(int i=head[key];i;i=h[i].next)if(h[i].num==x)return h[i].k; 42 | return -1; 43 | } 44 | void solve(int p){ 45 | if(p%G!=0)return; 46 | else{ 47 | p/=G; 48 | int tmp=Pow(g,phi/G),w=Pow(g,1LL*x*p%(phi/G)); 49 | for(int i=0;i=s){ 76 | for(int j=0;j2)){ 91 | phi/=2; 92 | if(b%4==3){ 93 | if(a%2==0)return; 94 | g=5;b=mo-b;G=exgcd(a,phi,x,y);if(x<0)x+=phi/G; 95 | bsgs(b); 96 | tacnt=0; 97 | for(int i=1;i<=Acnt;i++)if(Ans[i]%4==1)tans[++tacnt]=Ans[i]; 98 | Acnt=tacnt; 99 | for(int i=1;i<=Acnt;i++)Ans[i]=mo-tans[i]; 100 | }else{ 101 | g=5;G=exgcd(a,phi,x,y);if(x<0)x+=phi/G; 102 | bsgs(b); 103 | if(a%2==0){ 104 | int tmp=Acnt; 105 | for(int i=1;i<=tmp;i++)Ans[++Acnt]=mo-Ans[i]; 106 | } 107 | } 108 | return; 109 | } 110 | int tmp=pr-1;cnt=0; 111 | for(int i=1;prime[i]*prime[i]<=tmp;i++)if(tmp%prime[i]==0){ 112 | p[++cnt]=prime[i]; 113 | while(tmp%prime[i]==0)tmp/=prime[i]; 114 | } 115 | if(tmp>1)p[++cnt]=tmp; 116 | if(s>2)p[++cnt]=pr; 117 | g=find(pr);G=exgcd(a,phi,x,y);if(x<0)x+=phi/G; 118 | bsgs(b); 119 | } 120 | void merge(int Pow){ 121 | tacnt=0; 122 | exgcd(now,Pow,x,y); 123 | for(int i=1;i<=acnt;i++) 124 | for(int j=1;j<=Acnt;j++)tans[++tacnt]=(1LL*x*(Ans[j]-ans[i])%Pow+Pow)%Pow*now+ans[i]; 125 | acnt=tacnt;for(int i=1;i<=tacnt;i++)ans[i]=tans[i];now*=Pow; 126 | } 127 | int main(){ 128 | for(int i=2;i<=31700;i++){ 129 | if(!vis[i])prime[++cnt]=i; 130 | for(int j=1;(j<=cnt)&&(prime[j]*i<=31700);j++){ 131 | int k=prime[j]*i; 132 | vis[k]=true; 133 | if(i%prime[j]==0)break; 134 | } 135 | } 136 | test=read(); 137 | while(test--){ 138 | mo=read();a=read();b=read();size=int(sqrt(mo)+0.0000001); 139 | //x^a = b(mod mo) 140 | now=1;acnt=1;ans[1]=0; 141 | int tmp=mo; 142 | for(int i=1;(prime[i]*prime[i]<=tmp)&&(acnt);i++)if(tmp%prime[i]==0){ 143 | int s=0,S=1; 144 | while(tmp%prime[i]==0)tmp/=prime[i],s++,S*=prime[i]; 145 | Acnt=0; 146 | work(a,b,prime[i],s);merge(S); 147 | } 148 | if((tmp>1)&&(acnt))Acnt=0,work(a,b,tmp,1),merge(tmp); 149 | if(acnt){ 150 | sort(ans+1,ans+1+acnt); 151 | for(int i=1;i<=acnt;i++)printf("%d ",ans[i]); 152 | puts(""); 153 | }else puts("No Solution"); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /code04/N-res.cpp: -------------------------------------------------------------------------------- 1 | //51Nod - 1038 2 | //N次剩余 O(\sqrt{p}*大常数) 3 | typedef long long ll; 4 | struct pli{ 5 | ll first; 6 | int second; 7 | pli(){} 8 | pli (ll x_,int y_){ 9 | first=x_; 10 | second=y_; 11 | } 12 | bool operator < (const pli &b) const { 13 | if(first==b.first) return second>b.second;//因为second更大的解更小,所以重载> 14 | return first=0); 21 | // while(b) 22 | // { 23 | // if(b&1) res=(res+a)%c; 24 | // b>>=1; 25 | // a=(a+a)%c; 26 | // } 27 | // return res; 28 | //} 29 | ll fast_exp(ll a,ll b,ll c){ 30 | ll res=1; 31 | a=a%c; 32 | assert(b>=0); 33 | while(b>0) 34 | { 35 | if(b&1) res=(a*res)%c; 36 | b=b>>1; 37 | a=(a*a)%c; 38 | } 39 | return res; 40 | } 41 | vector a;//p-1的所有质因子 42 | bool g_test(ll g,ll p){ 43 | for(ll i=0;i hash; 87 | // for(int i=0;i!=M;++i){ 88 | // hash[base]=i;//存的是大的编号 89 | // base=base*a%m; 90 | // } 91 | // base=fast_exp(a,M,m);//必要时要用快速乘 92 | // ll now=t; 93 | // for(int i=1;i<=M+1;++i){ 94 | // now=now*base%m;//这里乘在左边了 相当于右边乘逆元 95 | // if(hash.count(now)) return i*M-hash[now]+cnt; 96 | // } 97 | pli hash[int(1e5)]; 98 | for(int i=0;i!=M;++i){ 99 | hash[i]=pli(base,i); 100 | base=base*a%m; 101 | } 102 | sort(hash,hash+M);//默认以first 103 | base=fast_exp(a,M,m); 104 | ll now=t; 105 | for(int i=1;i<=M+1;++i){ 106 | now=now*base%m; 107 | //找大于等于now的值 108 | //注意下面M+10 这样可以保证解最小,若设为-1,则找不到解了 109 | int id=lower_bound(hash,hash+M,pli(now,M+10))-hash;//默认是first 110 | assert(id>=0 &&id<=M); 111 | if(id!=M && hash[id].first==now) return i*M-hash[id].second+cnt;//减去的编号second越大越好 112 | } 113 | return -1; 114 | } 115 | ll e_gcd(ll a,ll b,ll &x,ll &y) 116 | { 117 | if(b==0){ 118 | x=1; 119 | y=0; 120 | return a; 121 | } 122 | ll ans=e_gcd(b,a%b,x,y); 123 | ll temp=x; 124 | x=y; 125 | y=temp-a/b*y; 126 | return ans; 127 | } 128 | vector residue(int p,int N,int a){ 129 | int g=primitive_root(p); 130 | ll t=bsgs(g,a,p); 131 | vector ans; 132 | if(a==0){ 133 | ans.push_back(0); 134 | return ans; 135 | } 136 | if(t==-1) return ans; 137 | //解不定方程 138 | ll A=N,B=p-1,C=t,x,y; 139 | ll d=e_gcd(A,B,x,y); 140 | if(C % d !=0) return ans; 141 | x=x*(C/d)%B; 142 | ll delta=B/d; 143 | for(int i=0;i>t; 159 | while(t--) 160 | { 161 | cin>>p>>N>>A; 162 | vector ans=residue(p,N,A); 163 | if(ans.size()){ 164 | for(int i=0;i>=1; 12 | a=(a*a)%c; 13 | } 14 | return res; 15 | } 16 | //p>2 17 | //x^2 = n (mod p) 18 | bool isqr(ll n, ll p) 19 | { 20 | if(fast_exp(n, (p-1)/2, p)!=1) return 0;//欧拉准则 21 | return true; 22 | } 23 | struct FP2{//Field p^2 24 | ll x,y; 25 | FP2 operator * (FP2 tmp) 26 | { 27 | FP2 ans; 28 | ans.x = (this->x * tmp.x %mod + this->y * tmp.y %mod * w2 %mod) %mod; 29 | ans.y = (this->x * tmp.y %mod + this->y * tmp.x %mod) %mod; 30 | return ans; 31 | } 32 | FP2 operator ^ (ll b) 33 | { 34 | FP2 ans; 35 | ans.x = 1; 36 | ans.y = 0;//幺元 37 | FP2 a = *this; 38 | while(b) 39 | { 40 | if(b&1) ans = ans*a; 41 | b>>=1; 42 | a = a*a; 43 | } 44 | return ans; 45 | } 46 | }; 47 | //return the smaller solution -1 means no solution 48 | //solve x^2 = n (mod p) 49 | ll solve(ll n, ll p) 50 | { 51 | mod = p; 52 | n=(n%p+p)%p; 53 | if(n==0) return 0;//这里考虑了解x=0的情况 注意[1,p-1]中解成对出现 而0只有一个解 54 | if(p==2) return n%p; 55 | if(!isqr(n,p)) return -1;//no solution 56 | ll x; 57 | if(p%4==3) x=fast_exp(n, (p+1)/4, p);//直接求解 58 | else{//Cipolla's algorithm 59 | //random find a s.t. a^2-n is non-quad-res 60 | ll a; 61 | srand(time(0)); 62 | while(1) 63 | { 64 | a = random(0,p-1); 65 | w2 = ((a*a-n)%p+p)%p; 66 | if(!isqr(w2,p)) break;//have found 67 | } 68 | FP2 ans; 69 | ans.x = a; 70 | ans.y = 1; 71 | ans = ans ^ ((p+1)/2); 72 | assert(ans.y==0);//FP^2 Field -> FP Field 73 | x = ans.x; 74 | } 75 | if(2*x>=p) x = p-x;//取较小解 76 | return x; 77 | } 78 | int main() 79 | { 80 | int t; 81 | cin>>t; 82 | while(t--) 83 | { 84 | ll n,p; 85 | cin>>n>>p; 86 | ll ans = solve(n,p); 87 | if(ans==-1) cout<<"Hola!"<=0 && ans

9) write(x/10); 28 | putchar(x%10+'0'); 29 | } 30 | //lll help1[maxn];//solve f(n/1) f(n/2) f(n/3) f(n/\sqrt(n)) 31 | //lll help2[maxn];//solve 1 2 3 \sqrt{n} 32 | const int maxn=21550000; 33 | ll g[maxn];//n^(2/3) g(n)=\sum_{i|n} i 34 | lll f[maxn];//sum_{i=1}^{n} [n/i]*i 35 | int ans[maxn/10]; 36 | int help[maxn];//存每个数最小质因子^指数 如12存2^2 18存2^1 32存2^5 37 | bool valid[maxn]; 38 | int tot; 39 | void get_prime(int n) 40 | { 41 | memset(valid,true,sizeof(valid)); 42 | tot=0; 43 | g[1]=1;help[1]=1; 44 | for(int i=2;i<=n;++i){ 45 | if(valid[i]){ 46 | ans[++tot]=i; 47 | g[i]=i+1; 48 | help[i]=i; 49 | } 50 | for(int j=1;j<=tot && ans[j]*i<=n;++j){ 51 | valid[ans[j]*i]=false; 52 | if(i%ans[j]==0){ 53 | help[i*ans[j]]=help[i]*ans[j]; 54 | g[i*ans[j]]=g[i]*ans[j]+g[i/help[i]]; 55 | break; 56 | } 57 | else{ 58 | help[i*ans[j]]=ans[j]; 59 | g[i*ans[j]]=g[i]*g[ans[j]]; 60 | } 61 | } 62 | } 63 | } 64 | int main() 65 | { 66 | get_prime(maxn); 67 | f[0]=0; 68 | for(int i=1;i>t; 73 | while(t--) 74 | { 75 | ll n; 76 | cin>>n; 77 | lll ans1=0; 78 | for(ll l=1,r;l<=n;l=r+1){ 79 | r=n/(n/l); 80 | ll tp=n/l; 81 | if(tp1 4 | //need p^0 and p^1 5 | typedef long long ll; 6 | const int mod=1e9+7; 7 | const int ni2=500000004; 8 | inline int add(const int x, const int v) { return x + v >= mod ? x + v - mod : x + v; } 9 | inline int dec(const int x, const int v) { return x - v < 0 ? x - v + mod : x - v; } 10 | inline int ff(ll x){ 11 | x%=mod; 12 | return x*(x+1)%mod*ni2%mod; 13 | } 14 | ll n,M; 15 | vector pre[2],hou[2],primes; 16 | 17 | int dfs(ll res, int last, ll f){ 18 | int g=dec((res > M ? hou[1][n/res] : pre[1][res]), pre[1][primes[last]-1]); 19 | //g(n/k) - g(L) L is largest prime in k ; g(L) = g(primes[last] - 1 ) 20 | int ret= f*g%mod; 21 | for(int i=last;i<(int) primes.size();++i){ 22 | int p = primes[i]; 23 | if((ll)p*p > res) break; 24 | for(ll q=p,nres=res,nf=f*(p-1)%mod;q*p<=res;q*=p){//nf开始为指数是1的贡献 nres无需修改 25 | ret = add(ret, dfs(nres/=p, i+1, nf));//枚举更大的数 26 | nf = nf*p%mod;//继续枚举当前素数,指数大于1时,指数每加1, nf=nf*p; 27 | ret =add(ret,nf); 28 | } 29 | } 30 | return ret; 31 | } 32 | ll solve(ll n){ 33 | M=sqrt(n); 34 | for(int i=0;i<2;++i){ 35 | pre[i].clear();pre[i].resize(M+1); 36 | hou[i].clear();hou[i].resize(M+1); 37 | } 38 | primes.clear();primes.reserve(M+1); 39 | for(int i=1;i<=M;++i){ 40 | pre[0][i]=i-1; 41 | hou[0][i]=(n/i-1)%mod; 42 | pre[1][i]=dec(ff(i),1);; 43 | hou[1][i]=dec(ff(n/i),1); 44 | } 45 | for(int p=2;p<=M;++p){ 46 | if(pre[0][p]==pre[0][p-1]) continue; 47 | primes.push_back(p); 48 | const ll q=(ll)p*p,m=n/p; 49 | const int pnt0=pre[0][p-1], pnt1=pre[1][p-1]; 50 | const int mid=M/p; 51 | const int End=min((ll)M, n/q); 52 | for(int i=1;i<=mid;++i){ 53 | hou[0][i]=dec(hou[0][i],dec(hou[0][i*p],pnt0)); 54 | hou[1][i]=dec(hou[1][i],dec(hou[1][i*p],pnt1)*(ll)p%mod); 55 | } 56 | for(int i=mid+1;i<=End;++i){ 57 | hou[0][i]=dec(hou[0][i],dec(pre[0][m/i],pnt0)); 58 | hou[1][i]=dec(hou[1][i],dec(pre[1][m/i],pnt1)*(ll)p%mod); 59 | } 60 | for(int i=M;i>=q;--i){ 61 | pre[0][i]=dec(pre[0][i],dec(pre[0][i/p],pnt0)); 62 | pre[1][i]=dec(pre[1][i],dec(pre[1][i/p],pnt1)*(ll)p%mod); 63 | } 64 | } 65 | primes.push_back(M+1); 66 | for (int i = 1; i <= M; i++) { 67 | pre[1][i] = dec(pre[1][i], pre[0][i]);//p-1 68 | hou[1][i] = dec(hou[1][i], hou[0][i]); 69 | } 70 | return n>1 ? add(dfs(n,0,1),1) : 1; 71 | }// 72 | int main(){ 73 | cin>>n; cout<1 4 | //need p^0 5 | typedef long long ll; 6 | ll n,M; 7 | vector pre,hou,primes; 8 | ll dfs(ll res, int last, ll f){ 9 | ll g=(res > M ? hou[n/res] : pre[res])-pre[primes[last]-1]; 10 | //g(n/k) - g(L) L is largest prime in k ; g(L) = g(primes[last] - 1 ) 11 | ll ret= f*g; 12 | //cout<<"now: "< res) break; 16 | for(ll q=p,nres=res,nf=f*(-1); q*p<=res ;q*=p){//nf开始为指数是1的贡献 nres无需修改 17 | ret += dfs(nres/=p, i+1, nf);//枚举更大的数 18 | //指数大于1时,无贡献 直接break 19 | break; 20 | } 21 | } 22 | return ret; 23 | } 24 | ll solve(ll n){ 25 | M=sqrt(n); 26 | pre.clear();pre.resize(M+1); 27 | hou.clear();hou.resize(M+1); 28 | primes.clear();primes.reserve(M+1); 29 | for(int i=1;i<=M;++i){ 30 | pre[i]=i-1; 31 | hou[i]=(n/i-1); 32 | } 33 | for(int p=2;p<=M;++p){ 34 | if(pre[p]==pre[p-1]) continue; 35 | primes.push_back(p); 36 | const ll q=(ll)p*p,m=n/p; 37 | const int pnt0=pre[p-1]; 38 | const int mid=M/p; 39 | const int End=min((ll)M, n/q); 40 | for(int i=1;i<=mid;++i) hou[i]=hou[i]-(hou[i*p]-pnt0); 41 | for(int i=mid+1;i<=End;++i) hou[i]=hou[i]-(pre[m/i]-pnt0); 42 | for(int i=M;i>=q;--i) pre[i]=pre[i]-(pre[i/p]-pnt0); 43 | } 44 | primes.push_back(M+1); 45 | for (int i = 1; i <= M; i++) { 46 | pre[i] = -pre[i];//-p^0 47 | hou[i] = -hou[i]; 48 | } 49 | return n>1 ? dfs(n,0,1)+1 : 1; 50 | }// 51 | int main(){ 52 | cin>>n; 53 | n--; 54 | ll ansa = solve(n); 55 | cin>>n; 56 | ll ansb = solve(n); 57 | cout< pre[2],hou[2]; 8 | vector primes; 9 | inline u64 ff(u64 x){ 10 | if(x&1) return (x+1)/2*x; 11 | return x/2*(x+1); 12 | } 13 | u64 dfs(u64 res, int last, u64 f){ 14 | u64 g,ans; 15 | if(last==0){//第一次进入dfs 统计所有素数的和 16 | g=(res > M ? hou[1][n/res] : pre[1][res])-pre[1][primes[last]-1]; 17 | ans = g; 18 | } 19 | else{ 20 | g=(res > M ? hou[0][n/res] : pre[0][res])-pre[0][primes[last]-1];//否则拿当前数的f值直接乘以素数个数即可 21 | ans = f*g; 22 | } 23 | for(int i=last;i<(int) primes.size();++i){ 24 | int p = primes[i]; 25 | if((u64)p*p > res) break; 26 | for(u64 q=p,nres=res,nf=(last==0? p: f); q*p<=res ;q*=p){//nf开始为指数是1的贡献 nres无需修改 27 | ans += dfs(nres/=p, i+1, nf);//枚举更大的数 28 | ans += nf; 29 | } 30 | } 31 | return ans; 32 | } 33 | u64 solve(u64 n){ 34 | M=sqrt(n); 35 | for(int i=0;i<2;++i){ 36 | pre[i].clear();pre[i].resize(M+1); 37 | hou[i].clear();hou[i].resize(M+1); 38 | } 39 | primes.clear();primes.reserve(M+1); 40 | for(int i=1;i<=M;++i){ 41 | pre[0][i]=i-1; 42 | hou[0][i]=(n/i)-1; 43 | pre[1][i]=ff(i)-1; 44 | hou[1][i]=ff(n/i)-1; 45 | } 46 | for(int p=2;p<=M;++p){ 47 | if(pre[0][p]==pre[0][p-1]) continue; 48 | primes.push_back(p); 49 | const u64 q=(u64)p*p, m=n/p; 50 | const u64 pnt0=pre[0][p-1], pnt1=pre[1][p-1]; 51 | const int mid=M/p; 52 | const int End=min(M, n/q); 53 | for(int i=1;i<=mid;++i){ 54 | hou[0][i]=hou[0][i]-(hou[0][i*p]-pnt0); 55 | hou[1][i]=hou[1][i]-(hou[1][i*p]-pnt1)*p; 56 | } 57 | for(int i=mid+1;i<=End;++i){ 58 | hou[0][i]=hou[0][i]-(pre[0][m/i]-pnt0); 59 | hou[1][i]=hou[1][i]-(pre[1][m/i]-pnt1)*p; 60 | } 61 | for(int i=M;i>=q;--i){ 62 | pre[0][i]=pre[0][i]-(pre[0][i/p]-pnt0); 63 | pre[1][i]=pre[1][i]-(pre[1][i/p]-pnt1)*p; 64 | } 65 | } 66 | primes.push_back(M+1); 67 | return n>1 ? dfs(n,0,0) : 0; 68 | }// 69 | int main(){ 70 | int t; cin>>t; 71 | while(t--){ 72 | cin>>n; 73 | cout< pre,hou; 8 | vector primes; 9 | u64 dfs(u64 res, int last, u64 f){ 10 | u64 g=(res > M ? hou[n/res] : pre[res])-pre[primes[last]-1]; 11 | u64 ret= f*g; 12 | for(int i=last;i<(int) primes.size();++i){ 13 | int p = primes[i]; 14 | if((u64)p*p > res) break; 15 | for(u64 q=p,nres=res,nf=f*(k+1); q*p<=res ;q*=p){//nf开始为指数是1的贡献 nres无需修改 16 | ret += dfs(nres/=p, i+1, nf);//枚举更大的数 17 | nf = nf + f*k; 18 | ret += nf; 19 | } 20 | } 21 | return ret; 22 | } 23 | u64 solve(u64 n){ 24 | M=sqrt(n); 25 | pre.clear();pre.resize(M+1); 26 | hou.clear();hou.resize(M+1); 27 | primes.clear();primes.reserve(M+1); 28 | for(int i=1;i<=M;++i){ 29 | pre[i]=i-1; 30 | hou[i]=(n/i-1); 31 | } 32 | for(int p=2;p<=M;++p){ 33 | if(pre[p]==pre[p-1]) continue; 34 | primes.push_back(p); 35 | const u64 q=(u64)p*p,m=n/p; 36 | const int pnt0=pre[p-1];//由于是素数个数 所以可以用int 不过这里要注意 37 | const int mid=M/p; 38 | const int End=min(M, n/q); 39 | for(int i=1;i<=mid;++i) hou[i]=hou[i]-(hou[i*p]-pnt0); 40 | for(int i=mid+1;i<=End;++i) hou[i]=hou[i]-(pre[m/i]-pnt0); 41 | for(int i=M;i>=q;--i) pre[i]=pre[i]-(pre[i/p]-pnt0); 42 | } 43 | primes.push_back(M+1); 44 | for (int i = 1; i <= M; i++) { 45 | pre[i] = pre[i]*(k+1); 46 | hou[i] = hou[i]*(k+1); 47 | } 48 | return n>1 ? dfs(n,0,1)+1 : 1; 49 | }// 50 | int main(){ 51 | int t; cin>>t; 52 | while(t--){ 53 | cin>>n>>k; 54 | cout<= mod ? x + v - mod : x + v;} 9 | inline int dec(const int x, const int v) {return x - v < 0 ? x - v + mod : x - v;} 10 | int n,nowk,M,tot; 11 | int dfs(int res, int last, int f){ 12 | int g = dec(res > M ? hou[n/res] : pre[res], pre[primes[last]-1]); 13 | int ans = 1LL*g*f%mod*nowk%mod; 14 | for(int i=last;i res) break; 17 | int num=1; 18 | for(ll q=p, nres=res, nf=1LL*f*nowk%mod; q*p<=res; q*=p){ 19 | ans = add(ans, dfs(nres/=p,i+1,nf)); 20 | nf = nf*(num + nowk)%mod*ni[num+1]%mod; 21 | num++; 22 | ans = add(ans, nf); 23 | } 24 | } 25 | return ans; 26 | } 27 | int solve(int n){ 28 | M=sqrt(n); 29 | tot=0; 30 | while (ll(M+1)*(M+1) <= n) M++; 31 | for(int i=1;i<=M;++i){ 32 | pre[i]=i-1; 33 | hou[i]=(n/i-1);//n 1e9 不用模 34 | inv[i] = 1./i; 35 | } 36 | for(int p=2;p<=M;++p){ 37 | if(pre[p]==pre[p-1]) continue; 38 | primes[tot++] = p; 39 | const int q=p*p, pnt=pre[p-1]; 40 | const int mid=M/p; 41 | const int End=min(M, n/q); 42 | for(int i=1;i<=mid;++i) hou[i]-=(hou[i*p]-pnt); 43 | const int m=n/p; 44 | for(int i=mid+1;i<=End;++i){ 45 | int j = m*inv[i] + 1e-6; 46 | hou[i]-=(pre[j]-pnt); 47 | } 48 | for(int i=M/p; i>=p ; --i) 49 | for(int j=p-1; j>=0;--j){ 50 | pre[i*p+j]-=(pre[i]-pnt); 51 | } 52 | } 53 | primes[tot++] = M+1; 54 | assert(n>1); 55 | return dfs(n,0,1); 56 | } 57 | void init() 58 | { 59 | ni[1]=1; 60 | Fac[0]=1; 61 | Fac_inv[0]=1; 62 | for(int i=1;i<40;++i){ 63 | if(i!=1) ni[i]=((mod-mod/i)*(ni[mod%i]))%mod; 64 | Fac[i]=Fac[i-1]*i%mod; 65 | Fac_inv[i]=Fac_inv[i-1]*ni[i]%mod; 66 | } 67 | } 68 | int main() 69 | { 70 | init(); 71 | int k; 72 | while(scanf("%d%d",&n,&k)!=EOF){ 73 | if(n==1 || k>=n){cout<<0<1){ 63 | res=(res*help[r][1])%mod; 64 | //cout<>n>>m; 50 | //cout<1) res-=res/x; 16 | return res; 17 | } -------------------------------------------------------------------------------- /code05/function.cpp: -------------------------------------------------------------------------------- 1 | const int mod=998244353; 2 | const int ni2=499122177; 3 | ll n,M; 4 | ll nmod; 5 | vector pre[2],hou[2],primes; 6 | inline int add(const int x, const int v) { 7 | return x + v >= mod ? x + v - mod : x + v; 8 | } 9 | inline int dec(const int x, const int v) { 10 | return x - v < 0 ? x - v + mod : x - v; 11 | } 12 | //这里res是n/枚举的数 13 | int dfs(ll res, int last, ll f, ll k){// k is now number 14 | //最大质因子是prime[last-1] 但将1放在外面值显然一样 15 | int t1 = nmod*dec((res > M ? hou[0][n/res] : pre[0][res]),pre[0][primes[last]-1])%mod; 16 | int t2 = k%mod*dec((res > M ? hou[1][n/res] : pre[1][res]),pre[1][primes[last]-1])%mod; 17 | int ret= (f+1)*dec(t1,t2)%mod; 18 | for(int i=last;i<(int) primes.size();++i){ 19 | int p = primes[i]; 20 | if((ll)p*p > res) break; 21 | for(ll q=p,nres=res,nk=k,nf=f+1;q*p<=res;q*=p){//nf需修改 22 | ret = add(ret,dfs(nres/=p,i+1,nf,nk*=p));//枚举更大的数 23 | nf+=1;//继续枚举当前素数 24 | ret =add(ret,nf*dec(nmod, k*q*p%mod)%mod);//指数大于1时,记上贡献 25 | } 26 | } 27 | return ret; 28 | } 29 | inline int ff(ll x){ 30 | x%=mod; 31 | return x*(x+1)%mod*ni2%mod; 32 | } 33 | int solve(ll n){ 34 | M=sqrt(n); 35 | for(int i=0;i<2;++i){ 36 | pre[i].clear();pre[i].resize(M+1); 37 | hou[i].clear();hou[i].resize(M+1); 38 | } 39 | primes.clear();primes.reserve(M+1); 40 | for(int i=1;i<=M;++i){ 41 | pre[0][i]=i-1; 42 | hou[0][i]=(n/i-1)%mod; 43 | pre[1][i]=dec(ff(i),1);; 44 | hou[1][i]=dec(ff(n/i),1); 45 | } 46 | for(int p=2;p<=M;++p){ 47 | if(pre[0][p]==pre[0][p-1]) continue; 48 | primes.push_back(p); 49 | const ll q=(ll)p*p,m=n/p; 50 | const int pnt0=pre[0][p-1],pnt1=pre[1][p-1]; 51 | const int mid=M/p; 52 | const int End=min((ll)M,n/q); 53 | for(int i=1;i<=mid;++i){ 54 | hou[0][i]=dec(hou[0][i],dec(hou[0][i*p],pnt0)); 55 | hou[1][i]=dec(hou[1][i],dec(hou[1][i*p],pnt1)*(ll)p%mod); 56 | } 57 | for(int i=mid+1;i<=End;++i){ 58 | hou[0][i]=dec(hou[0][i],dec(pre[0][m/i],pnt0)); 59 | hou[1][i]=dec(hou[1][i],dec(pre[1][m/i],pnt1)*(ll)p%mod); 60 | } 61 | for(int i=M;i>=q;--i){ 62 | pre[0][i]=dec(pre[0][i],dec(pre[0][i/p],pnt0)); 63 | pre[1][i]=dec(pre[1][i],dec(pre[1][i/p],pnt1)*(ll)p%mod); 64 | } 65 | } 66 | //cout<1 ? add(dfs(n,0,0,1),0) : 0; 69 | } 70 | int main() 71 | { 72 | ios::sync_with_stdio(false); 73 | cin>>n; 74 | nmod = (n+1)%mod; 75 | cout<>n>>m; 55 | cout<>n) cout<1) return 0; 10 | x/=ans[j]; 11 | } 12 | cnt++; 13 | } 14 | } 15 | if(x!=1) cnt++; 16 | return (cnt&1)?-1:1; 17 | } -------------------------------------------------------------------------------- /code05/nowcode-Function.cpp: -------------------------------------------------------------------------------- 1 | //f(1)=1 2 | //f(p)= (p%4==1)? 4 : 1 3 | //f(p^e) = (p%4==1)? 3e+1 : 1 4 | #include 5 | using namespace std; 6 | typedef long long ll; 7 | int n,M; 8 | vector pre[2],hou[2],primes; 9 | //0 1~n 素数个数 10 | //1 1~n中 %4=1素数个数 减去 %4=3素数个数 11 | ll dfs(ll res, int last, ll f){ 12 | ll t=(res > M ? hou[0][n/res] : pre[0][res])-pre[0][primes[last]-1]; 13 | ll ans= t*f; 14 | for(int i=last;i<(int) primes.size();++i){ 15 | int p = primes[i]; 16 | if(p*p > res) break; 17 | for(ll q=p, nres=res, nf=f*(((p&3)==1)?4:1);q*p<=res;q*=p){ 18 | ans += dfs (nres/=p,i+1,nf); 19 | nf += f*(((p&3)==1)?3:0); 20 | ans += nf; 21 | } 22 | } 23 | return ans; 24 | } 25 | int help1[4]={0,1,1,0}; 26 | int help2[4]={0,1,0,-1}; 27 | ll solve(int n){ 28 | M=sqrt(n); 29 | for(int i=0;i<2;++i){ 30 | pre[i].clear();pre[i].resize(M+1); 31 | hou[i].clear();hou[i].resize(M+1); 32 | } 33 | primes.clear();primes.reserve(M+1); 34 | for(int i=1;i<=M;++i){ 35 | pre[0][i]=i-1; 36 | hou[0][i]=n/i-1; 37 | pre[1][i]=help1[i&3]-1;//kafang function 38 | hou[1][i]=help1[(n/i)&3]-1; 39 | } 40 | for(int p=2;p<=M;++p){ 41 | if(pre[0][p]==pre[0][p-1]) continue; 42 | primes.push_back(p); 43 | const int q=p*p,m=n/p, pnt0 = pre[0][p-1], pnt1 = pre[1][p-1]; 44 | const int mid=M/p; 45 | const int End=min(M,n/q); 46 | for(int i=1;i<=mid;++i){ 47 | hou[0][i]-=hou[0][i*p]-pnt0; 48 | hou[1][i]-=(hou[1][i*p]-pnt1)*help2[p&3]; 49 | } 50 | for(int i=mid+1;i<=End;++i){ 51 | hou[0][i]-=pre[0][m/i]-pnt0; 52 | hou[1][i]-=(pre[1][m/i]-pnt1)*help2[p&3]; 53 | } 54 | for(int i=M;i>=q;--i){ 55 | pre[0][i]-=pre[0][i/p]-pnt0; 56 | pre[1][i]-=(pre[1][i/p]-pnt1)*help2[p&3]; 57 | } 58 | } 59 | primes.push_back(M+1); 60 | //由0 和 1 可推出 1~n中模4余1的素数个数 覆盖入1中 61 | for (int i = 2; i <= M; ++i) { 62 | assert((pre[0][i] + pre[1][i] - 1)%2==0); 63 | assert((hou[0][i] + hou[1][i] - 1)%2==0); 64 | pre[1][i] = (pre[0][i] + pre[1][i] - 1)/2; 65 | hou[1][i] = (hou[0][i] + hou[1][i] - 1)/2; 66 | } 67 | hou[1][1] = (hou[0][1] + hou[1][1] - 1)/2; 68 | //对于本题 计算的贡献为3*pre[1] + pre[0] 69 | //覆盖到0中 70 | for(int i = 1;i <= M; ++i){ 71 | pre[0][i]+=3*pre[1][i]; 72 | hou[0][i]+=3*hou[1][i]; 73 | } 74 | return n>1 ? 1+dfs(n,0,1) : 1; 75 | } 76 | int main() 77 | { 78 | int t;cin>>t;while(t--) 79 | {cin>>n;cout<>=1; a=mod_mul(a,a,c); 26 | } 27 | return res; 28 | } 29 | bool test(ll n,ll a,ll d) 30 | {//d=n-1 31 | if(!(n&1)) return false; 32 | while(!(d&1)) d>>=1;//将d分解为奇数 至少有一个2因子,所以d!=n-1 33 | ll t=fast_exp(a,d,n); 34 | while(d!=n-1 && t!=1 && t!=n-1){ 35 | t=mod_mul(t,t,n); 36 | d<<=1; 37 | } 38 | return ((t==n-1) || (d&1) ==1 );//两个条件都不成立则一定是合数; 若两个有一个成立,且多次,对于合数来说,可能性极小,所以可以认为是素数 39 | } 40 | bool is_prime(ll n){ 41 | if(n<2) return false; 42 | for(int i=1;i<=tot;++i){ 43 | if(n==primes[i]) return true; 44 | if(n%primes[i]==0) return false; 45 | } 46 | ll a[10]; 47 | srand(time(0)); 48 | for(int i=0;i<6;++i) {//测试6次 49 | a[i]=rand()%(n-2)+2;//取[2,n-1]随机数 50 | if(!test(n,a[i],n-1)) return false;//找到证据说明是合数 51 | } 52 | return true;//如果上面所有测试都是true 53 | } 54 | ll solve(ll n) 55 | { 56 | vector pre, hou; 57 | vector primes; 58 | vector inv; 59 | int M=sqrt(n); 60 | pre.resize(M+1+sqrt(M)+1); 61 | hou.resize(M+1); 62 | inv.resize(M+1); 63 | primes.reserve(M+1); 64 | for(int i=1;i<=M;++i){ 65 | pre[i]=i-1; 66 | hou[i]=(n/i-1); 67 | inv[i]=1./i; 68 | } 69 | for(int p=2;p<=M;++p){ 70 | if(pre[p]==pre[p-1]) continue; 71 | primes.push_back(p); 72 | const ll q=(ll)p*p,m=n/p; 73 | const int pnt0=pre[p-1]; 74 | const int mid=M/p; 75 | const int End=min((ll)M, n/q); 76 | for(int i=1;i<=mid;++i) hou[i]-=hou[i*p]-pnt0; 77 | for(int i=mid+1;i<=End;++i){ 78 | int j = m*inv[i] + 1e-6; 79 | hou[i]-=pre[j]-pnt0; 80 | } 81 | for(int i=M/p;i>=p;--i){ 82 | for(int j=p-1;j>=0;--j) 83 | pre[i*p+j]-=pre[i]-pnt0; 84 | } 85 | } 86 | return hou[1]; 87 | } 88 | ll nthprime(ll n) 89 | { 90 | ll l=2-1, r=n*23;//问题区间[2,x] 由于是左开右闭 所以l为2 -1 91 | //23 是对于1e9测试出来的 因为输入1e9 答案是22801763489 92 | int num = 0; 93 | ll res; 94 | while(r-l>1) 95 | { 96 | ll mid=(l+r)/2; 97 | res = solve(mid); 98 | if(res>=n){ 99 | r=mid; 100 | if(num>=16) break; 101 | } 102 | else l=mid; 103 | num++; 104 | } 105 | //cout<n 110 | } 111 | } 112 | } 113 | int main() 114 | { 115 | getprime(100, primes);//100以内的素数 用于加速miller-rabin 116 | ll n; 117 | cin>>n;// 1<= n <= 10^9 118 | //n=1e9; 119 | cout< pre, hou; 5 | vector primes; 6 | vector inv; 7 | int M=sqrt(n); 8 | pre.resize(M+1+sqrt(M)+1); 9 | hou.resize(M+1); 10 | inv.resize(M+1); 11 | primes.reserve(M+1); 12 | for(int i=1;i<=M;++i){ 13 | pre[i]=i-1; 14 | hou[i]=(n/i-1); 15 | inv[i]=1./i; 16 | } 17 | for(int p=2;p<=M;++p){ 18 | if(pre[p]==pre[p-1]) continue; 19 | primes.push_back(p); 20 | const ll q=(ll)p*p,m=n/p; 21 | const int pnt0=pre[p-1]; 22 | const int mid=M/p; 23 | const int End=min((ll)M, n/q); 24 | for(int i=1;i<=mid;++i) hou[i]-=hou[i*p]-pnt0; 25 | for(int i=mid+1;i<=End;++i){ 26 | int j = m*inv[i] + 1e-6; 27 | hou[i]-=pre[j]-pnt0; 28 | } 29 | for(int i=M/p;i>=p;--i){ 30 | for(int j=p-1;j>=0;--j) 31 | pre[i*p+j]-=pre[i]-pnt0; 32 | } 33 | } 34 | return hou[1]; 35 | } 36 | double upper_func(ll x)//1~x中素数的个数估计 上界 37 | { 38 | if(x >= 2953652287){ 39 | double lgx = log((double)x); 40 | double lgx2 = lgx * lgx; 41 | return x/lgx*(1+1/lgx+2.334/lgx2); 42 | } 43 | else return solve(x); 44 | } 45 | double lower_func(ll x) 46 | { 47 | if(x >= 88783){ 48 | double lgx = log((double)x); 49 | double lgx2 = lgx * lgx; 50 | return x/lgx*(1+1/lgx+2.0/lgx2); 51 | } 52 | else return solve(x); 53 | } 54 | const int maxn=2e5;//2e5 平方之后肯定 > 1e9*23 + 1.5*1e7 55 | bool valid[maxn+10]; 56 | ll ans[maxn/5]; 57 | bool vis[maxn*75];//用于被筛 1.5*1e7 够用了 58 | int tot; 59 | void get_prime(int n) 60 | { 61 | tot=0; 62 | for(int i=2;i<=n;++i) valid[i]=true; 63 | for(int i=2;i<=n;++i){ 64 | if(valid[i]) ans[++tot]=i; 65 | for(int j=1;j<=tot && ans[j]*i<=n;++j){ 66 | valid[ans[j]*i]=false; 67 | if(i%ans[j]==0) break; 68 | } 69 | } 70 | } 71 | ll nthprime(ll n) 72 | { 73 | ll l,r,up,low; 74 | l=2-1, r=1e9*23;//1e9 *23就够了 75 | while(r-l>1) 76 | { 77 | ll mid = (l+r)/2; 78 | if(lower_func(mid)>=n) r = mid; 79 | else l = mid; 80 | } 81 | up = r;//up比实际值大 82 | l = 2-1; 83 | while(r-l>1) 84 | { 85 | ll mid=(l+r)/2; 86 | if(upper_func(mid)>=n) r=mid; 87 | else l=mid; 88 | } 89 | low = r; 90 | //cout<>n;// 1<= n <= 10^9 120 | cout< vec; 15 | while(x){ 16 | int tp = x%10; 17 | vec.push_back(tp); 18 | x/=10; 19 | } 20 | for(int i=vec.size()-1;i>=0;--i){ 21 | cout<=p ; --i) 52 | for(int j=p-1; j>=0;--j){ 53 | pre[i*p+j]-=(pre[i]-pnt)*p; 54 | } 55 | } 56 | return hou[1]; 57 | } 58 | double upper_func(__int128 x) 59 | { 60 | if(x >= 110118925){ 61 | double x2 = x*x; 62 | double lgx = log((double)x); 63 | double lgx2 = lgx * lgx; 64 | double lgx3 = lgx2 * lgx; 65 | double lgx4 = lgx3 * lgx; 66 | return x2/(2*lgx) + x2/(4*lgx2) + x2/(4*lgx3) + 5.3*x2/(8*lgx4); 67 | } 68 | else return solve(x); 69 | } 70 | double lower_func(__int128 x) 71 | { 72 | if(x >= 905238547){ 73 | double x2 = x*x; 74 | double lgx = log((double)x); 75 | double lgx2 = lgx * lgx; 76 | double lgx3 = lgx2 * lgx; 77 | double lgx4 = lgx3 * lgx; 78 | return x2/(2*lgx) + x2/(4*lgx2) + x2/(4*lgx3) + 1.2*x2/(8*lgx4); 79 | } 80 | else return solve(x); 81 | } 82 | const int maxn=(1<<20);//1e6+x 平方之后肯定够用了 83 | bool valid[maxn+10]; 84 | ll ans[maxn/5]; 85 | bool vis[maxn*25];//用于被筛 2.5*1e7 够用了 86 | int tot; 87 | void get_prime(int n) 88 | { 89 | tot=0; 90 | for(int i=2;i<=n;++i) valid[i]=true; 91 | for(int i=2;i<=n;++i){ 92 | if(valid[i]) ans[++tot]=i; 93 | for(int j=1;j<=tot && ans[j]*i<=n;++j){ 94 | valid[ans[j]*i]=false; 95 | if(i%ans[j]==0) break; 96 | } 97 | } 98 | } 99 | int main() 100 | { 101 | ios::sync_with_stdio(false); 102 | string str; 103 | cin>>str; 104 | S = scanf128(str); 105 | ll l,r,up,low; 106 | l = 0, r = 1e12 + 1e10; 107 | while(r-l>1) 108 | { 109 | ll mid=(l+r)/2; 110 | if(lower_func(mid)>=S) r=mid; 111 | else l=mid; 112 | } 113 | up = r;//up比实际值大 114 | l = 0; 115 | while(r-l>1) 116 | { 117 | ll mid=(l+r)/2; 118 | if(upper_func(mid)>=S) r=mid; 119 | else l=mid; 120 | } 121 | low = r; 122 | if(up==low) cout<>t; 72 | while(t--) 73 | { 74 | int n; 75 | cin>>n; 76 | m=n; 77 | if(n<=up) cout< 2 | using namespace std; 3 | #define ll __int128 4 | const int maxn=100; 5 | const int maxm =2e6+10; 6 | ll a[maxn];//质因子 7 | int b[maxn];//质因子的指数 8 | int tot;//1~tot 9 | bool valid[maxm]; 10 | int prime[maxm]; 11 | void getprime(int n,int &tot,int ans[]) 12 | { 13 | tot=0; 14 | memset(valid,true,sizeof(valid)); 15 | for(int i=2;i<=n;++i){ 16 | if(valid[i]){ 17 | tot++; 18 | ans[tot]=i; 19 | } 20 | //下面的主角是小于等于i的每个质数 21 | for(int j=1;(j<=tot) && (i*ans[j]<=n);++j){ 22 | valid[i*ans[j]]=false; 23 | if(i%ans[j]==0) break;//如果整除就break; 24 | } 25 | } 26 | } 27 | void factor(ll n) 28 | { 29 | ll now=n; 30 | tot=0; 31 | for(int i=1;1LL*prime[i]*prime[i]<=now;++i) if(now%prime[i]==0){ 32 | a[++tot]=prime[i];b[tot]=0; 33 | while(now%prime[i]==0){++b[tot];now/=prime[i];} 34 | } 35 | if(now>1){ 36 | a[++tot]=now;b[tot]=1; 37 | } 38 | } 39 | ll e_gcd(ll a,ll b,ll &x,ll &y) 40 | { 41 | if(b==0){x=1;y=0;return a;} 42 | ll ans=e_gcd(b,a%b,x,y); 43 | ll temp=x; x=y; y=temp-a/b*y; 44 | return ans; 45 | } 46 | ll newinv(ll a,ll mod) 47 | { 48 | ll ans,tmp; 49 | e_gcd(a,mod,ans,tmp); 50 | //gcd(a,mod) = 1 51 | ll c = mod-1; 52 | ans = (c*ans%mod+mod)%mod; 53 | return ans; 54 | } 55 | int main() 56 | { 57 | int Count; 58 | getprime(2e6,Count, prime); 59 | int t; 60 | cin>>t; 61 | while(t--) 62 | { 63 | long long n; 64 | cin>>n; 65 | if(n==1){cout<<1<1 67 | assert(tot<15); 68 | ll ans=1e18; 69 | for(int i=0;i<(1<>=1; 79 | } 80 | B = 2*n/A; 81 | ll ni = newinv(A,B); 82 | ll ans1 = A*ni; 83 | if(ans1 == 0) ans1 = 1e18; 84 | ans = min(ans1,ans); 85 | } 86 | cout<<(long long)ans<up && is_prime(num+1)){//如果当前因子num比\sqrt{n}大 31 | all=min(all,phi*(num+1)); 32 | return ; 33 | } 34 | for(int i=last+1;i<=tot && ans[i]<=num;i++) 35 | if(num%(ans[i]-1)==0){//如果num能整除当前素数-1 36 | ll num_ = num/(ans[i]-1);//除去 37 | ll phi_ = phi*ans[i];//搞上 38 | dfs(i,num_,phi_); 39 | while(num_%ans[i]==0){//同时phi可以有更多的该素数 40 | num_/=ans[i]; 41 | phi_*=ans[i]; 42 | dfs(i,num_,phi_); 43 | } 44 | } 45 | } 46 | int main() 47 | { 48 | get_prime(1e5);//1e5很稳 49 | ll n; 50 | cin>>n; 51 | up=floor(sqrt(n));//注意至少要加1 52 | all=1LL<<31; 53 | dfs(0,n,1); 54 | if(all<(1LL<<31)) cout< pll; 2 | #define mp make_pair 3 | ll fun45(double x){if(x>=0) return x+0.5;return x-0.5;} 4 | ll mod_mul(ll a,ll b,ll c){__int128 tp=1;return tp*a*b%c;} 5 | ll fast_exp(ll a,ll b,ll c){ 6 | ll res=1;a=a%c;assert(b>=0); 7 | while(b>0) 8 | { 9 | if(b&1) res=mod_mul(a,res,c); 10 | b=b>>1;a=mod_mul(a,a,c); 11 | } 12 | return res; 13 | } 14 | ll ran(ll n){//求解x^2 \equiv -1 (mod n) //要保证n%4=1 15 | assert(n%4==1); 16 | srand(time(0)); 17 | for(;;){ 18 | ll a=rand()%(n-1)+1;//1~ n-1 19 | ll b=fast_exp(a, (n-1)/4 ,n); 20 | if(mod_mul(b,b,n)==n-1) return b<=(n-1)/2 ? b:n-b; 21 | } 22 | } 23 | pll solveprime(ll p) 24 | { 25 | if(p==2) return mp(1,1); 26 | if(p%4!=1) return mp(-1,-1); 27 | ll A,B,u,v,M; 28 | A=ran(p); B=1; 29 | __int128 tmp=1; 30 | M=(tmp*A*A+tmp*B*B)/p; 31 | assert(M1) 33 | { 34 | u=(A%M+M)%M;v=(B%M+M)%M; 35 | if(2*u>M) u-=M;//注意可能是负数 36 | if(2*v>M) v-=M; 37 | assert(u<=M/2 && u>=-M/2); 38 | assert(v<=M/2 && v>=-M/2); 39 | ll ta=A; 40 | A=(tmp*u*A+tmp*v*B)/M; 41 | B=(tmp*v*ta-tmp*u*B)/M; 42 | M=(tmp*u*u+tmp*v*v)/M; 43 | } 44 | return make_pair(min(abs(A),abs(B)),max(abs(A),abs(B))); 45 | } 46 | const int maxn = 100; 47 | ll a[maxn];//质因子 48 | int b[maxn];//质因子的指数 49 | int tot;//1~tot 50 | void factor(ll n) 51 | { 52 | assert(n>=1); 53 | int now=n; 54 | tot=0; 55 | for(int i=2;i*i<=now;++i) if(now%i==0){ 56 | a[++tot]=i; 57 | b[tot]=0; 58 | while(now%i==0){ 59 | ++b[tot]; 60 | now/=i; 61 | } 62 | } 63 | if(now>1){a[++tot]=now;b[tot]=1;} 64 | } 65 | vector ans; 66 | vector bns; 67 | vector cns;//每个模4余1的素数的指数 68 | vector > pre;//维护每个模4余1的素数的a-bi前缀乘积 69 | vector > p3;//size = 4 %4=3 和 2 的贡献 70 | void dfs(int last, complex now) 71 | { 72 | if(last==cns.size()){ 73 | //一种方案 可以扩展为4种 即分别乘以p3 -p3 p3i -p3i 74 | //cout< tp = p3[i]*now; 77 | ans.push_back(mp(fun45(tp.real()), fun45(tp.imag()))); 78 | } 79 | return ; 80 | } 81 | complex z{1,0}; 82 | complex z1{1.0*bns[last].first, 1.0*bns[last].second}; 83 | complex z2 = pre[last]; 84 | complex z3{1.0*bns[last].first, -1.0*bns[last].second}; 85 | for(int i=0;i<=cns[last];++i){ 86 | dfs(last+1, now*z*z2); 87 | z = z*z1; 88 | z2 = z2/z3; 89 | } 90 | } 91 | void solve(ll r) 92 | { 93 | ans.clear();bns.clear();cns.clear(); 94 | pre.clear();p3.clear(); 95 | //对r质因子分解 96 | factor(r); 97 | for(int i=1;i<=tot;++i) b[i]*=2;//r^2质因子分解 98 | ll tp=1; //对那些模4余3的质数 平分贡献 99 | for(int i=1;i<=tot;++i) if((a[i]&3) == 3) tp=tp*fast_exp(a[i], b[i]>>1 , 1e18); 100 | int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; 101 | complex z; 102 | for(int i=0;i<4;++i){ 103 | z.imag(tp*dir[i][0]); 104 | z.real(tp*dir[i][1]); 105 | if(a[1]==2 && tot>=1){//2的贡献加进去 tot>=1是排除n=1的情况 106 | complex z1{1.0,1.0}; 107 | z = z*pow(z1,b[1]); 108 | } 109 | p3.push_back(z); 110 | } 111 | for(int i=1;i<=tot;++i){//对那些模4余1的质数求出它们的解 并且有 指数+1 种情况 112 | if((a[i]&3) == 1){ 113 | pll tp = solveprime(a[i]); 114 | bns.push_back(tp); 115 | z.real(tp.first); 116 | z.imag(-tp.second); 117 | pre.push_back(pow(z,b[i])); 118 | cns.push_back(b[i]); 119 | } 120 | } 121 | z.imag(0);z.real(1); 122 | dfs(0, z); 123 | //check ans 124 | ll res =1; 125 | for(int i=1;i<=tot;++i) 126 | { 127 | if((a[i]&3) == 1) res*=(b[i]+1); 128 | else if((a[i]&3) == 3 && (b[i]&1)==1){ 129 | res = 0; 130 | break; 131 | } 132 | } 133 | res*=4; 134 | assert(res == ans.size());//由于这里求得是r^2的情况 所以一定存在 135 | //当然任意的N都可以求 136 | } 137 | int main() 138 | { 139 | ll r; 140 | cin>>r; 141 | solve(r); 142 | if(ans.size()==0){ 143 | cout<<"no solution"< 2 | using namespace std; 3 | typedef long long ll; 4 | const int maxn=1e6+10; 5 | const int mod = 998244353; 6 | const int groot = 3;//119 * 2^23 + 1 = mod 7 | int gk = 119; 8 | const int limit = 1<<23; 9 | int omega[maxn<<2], omegaInverse[maxn<<2];//辅助 10 | ll fast_exp(ll a,ll b,ll c){ 11 | ll res=1; 12 | a=a%c; 13 | assert(b>=0); 14 | while(b>0) 15 | { 16 | if(b&1) res=(a*res)%c; 17 | b=b>>1; 18 | a=(a*a)%c; 19 | } 20 | return res; 21 | } 22 | inline int add(const int x, const int v){ 23 | return x+v>=mod?x+v-mod: x+v; 24 | } 25 | inline int dec(const int x, const int v){ 26 | return x-v<0?x-v+mod:x-v; 27 | } 28 | void init(const int n){ 29 | omega[0] = 1; 30 | omegaInverse[0] = 1; 31 | assert(limit>=n); 32 | gk = gk* (limit / n);// 很关键 取到恰好用的2^m 33 | int g_n = fast_exp(groot, gk, mod); 34 | int g_n_ni = fast_exp(g_n, mod-2, mod); 35 | for(int i=1;i>n>>m; 78 | n++;m++; 79 | for(int i=0;i>a[i]; 80 | for(int i=0;i>b[i]; 81 | int N=1; 82 | while(N sta; 6 | int up = 3000; 7 | int n=3000; 8 | ll fast_exp(ll a,ll b,ll c){ 9 | ll res=1;a=a%c; 10 | while(b) 11 | { 12 | if(b&1) res=(res*a)%c; 13 | b>>=1;a=(a*a)%c; 14 | } 15 | return res; 16 | } 17 | void solve(int len) 18 | { 19 | int len2 = sta.size(); 20 | assert(len2>1); 21 | int len1 = len-len2; 22 | ll res = help[len]*helpni[len1]%mod; 23 | stack sta_tp = sta; 24 | vector vec; 25 | while(!sta_tp.empty()){ 26 | vec.push_back(sta_tp.top()); 27 | sta_tp.pop(); 28 | } 29 | vec.push_back(-1); 30 | int num=1; 31 | for(int i=1;i=3){ 46 | //1的数量: jie-sum >1的数量: len 47 | //ans[tp]++; 48 | //取当前的sta 然后计算 49 | solve(tp); 50 | } 51 | else ; 52 | } 53 | else return; 54 | int low; 55 | if(sta.empty()) low = 2; 56 | else low = sta.top(); 57 | for(int i=low;i<=up;++i){ 58 | sta.push(i); 59 | dfs(len+1, sum+i,jie*i); 60 | sta.pop(); 61 | } 62 | } 63 | void init() 64 | { 65 | help[1] = 1;helpni[1] = 1; 66 | for(int i=2;i<=3000;++i){ 67 | help[i] = help[i-1]*i%mod; 68 | helpni[i] = fast_exp(help[i], mod-2,mod); 69 | } 70 | } 71 | int main() 72 | { 73 | init(); 74 | dfs(0,0,1); 75 | int t; 76 | cin>>t; 77 | while(t--) 78 | { 79 | int m; 80 | cin>>m; 81 | if(m==2) cout<<1< 2 | using namespace std; 3 | typedef long long ll; 4 | const double pi=acos(-1.0); 5 | const int maxn=1e5; 6 | typedef complex xu; 7 | xu omega[maxn<<2],omegaInverse[maxn<<2];//辅助 8 | void init(const int n){ 9 | for(int i=0;i>n>>m; 52 | n++; 53 | m++; 54 | int tp; 55 | for(int i=0;i>tp; 57 | a[i]=xu(tp,0); 58 | } 59 | for(int i=0;i>tp; 61 | b[i]=xu(tp,0); 62 | } 63 | int N=1; 64 | while(N 2 | using namespace std; 3 | typedef long long ll; 4 | const double pi=acos(-1.0); 5 | const int maxn=1e5; 6 | typedef complex xu; 7 | xu a[maxn<<2],b[maxn<<2]; 8 | int inver; 9 | void FFT(xu *s ,int n){ 10 | if(n==1) return ;//n=1时值就是系数 因为只有w_1^0这一点 11 | xu a1[n>>1],a2[n>>1]; 12 | for(int i=0;i>1]=s[i]; 14 | a2[i>>1]=s[i+1]; 15 | } 16 | FFT(a1,n>>1); FFT(a2,n>>1); 17 | xu w=xu(cos(2*pi/n),inver*sin(2*pi/n)); 18 | xu wn=xu(1,0); 19 | for(int i=0;i<(n>>1);++i,wn=wn*w){ 20 | s[i]=a1[i]+wn*a2[i]; 21 | s[i+(n>>1)]=a1[i]-wn*a2[i]; 22 | } 23 | } 24 | int main() 25 | { 26 | #ifndef ONLINE_JUDGE 27 | freopen("in.txt","r",stdin); 28 | #endif 29 | ios::sync_with_stdio(false); 30 | int n,m; 31 | cin>>n>>m; 32 | int tp; 33 | n++; 34 | m++; 35 | for(int i=0;i>tp; 37 | a[i]=xu(tp,0); 38 | } 39 | for(int i=0;i>tp; 41 | b[i]=xu(tp,0); 42 | } 43 | int N=1; while(N>=1; a=(a*a)%c; 8 | } 9 | return res; 10 | } 11 | ll phi(ll x) 12 | { 13 | if(x==1) return 1; 14 | ll res=x; 15 | for(int i=2;i*i<=x;++i){ 16 | if(x%i==0){ 17 | res-=res/i; 18 | do{ 19 | x/=i; 20 | }while(x%i==0); 21 | } 22 | } 23 | if(x>1) res-=res/x; 24 | return res; 25 | } 26 | //返回 循环节前面有多少位 以及 最小循环节长度 27 | //ans的size为1表示有限小数 28 | //ans的size为2表示无限循环小数 29 | vector solve(ll p, ll q) 30 | { 31 | p = p%q; 32 | ll d = gcd(p,q); 33 | p/=d; 34 | q/=d; 35 | vector ans; 36 | ll m=q; //看q里有多少个2和5 37 | int num2=0, num5=0; 38 | while(!(m&1)){ 39 | m>>=1; num2++; 40 | } 41 | while(m%5==0){ 42 | m/=5; num5++; 43 | } 44 | ll i = max(num2,num5),j=-1; 45 | //10^{j-i} \equiv 1 (mod m) 46 | //so solve 10^x = 1 (mod m) and j=x+i 47 | ll varphi = phi(m); 48 | vector fac1;//从小到大 49 | vector fac2;//从大到小 50 | for(ll x=1;x*x<=varphi;++x){ 51 | if(varphi%x==0){ 52 | fac1.push_back(x); 53 | fac2.push_back(varphi/x); 54 | } 55 | } 56 | reverse(fac2.begin(), fac2.end()); 57 | fac1.insert(fac1.end(),fac2.begin(),fac2.end()); 58 | for(int k=0;k>p>>q; 77 | ll zheng = p/q; 78 | p = p%q; 79 | vector ans = solve(p,q); 80 | cout<1){ 95 | cout<<"("; 96 | int num2 = ans[1]; 97 | while(num2){ 98 | cout<0){ 109 | cout<=tot) return ; 26 | if(num>ans){ 27 | res=cur;//当前数 28 | ans=num;//约数个数 29 | } 30 | else if(num==ans){ 31 | res=min(res,cur); 32 | } 33 | for(int i=1;i<=61 && i<=pre;++i){//枚举指数 34 | if(cur<=up/prime[last+1]){ 35 | //cout<>t; 49 | while(t--) 50 | { 51 | ans=0; 52 | cin>>up; 53 | dfs(0,1,1,61); 54 | cout< 2 | using namespace std; 3 | int main() 4 | { 5 | stringstream ss; 6 | streambuf* buffer = cout.rdbuf(); //oldbuffer,STDOUT的缓冲区 7 | cout.rdbuf(ss.rdbuf());//redirect 8 | 9 | cout<<"number theory"<