数学小专题

时间:2019-09-23
本文章向大家介绍数学小专题,主要包括数学小专题使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

简单复习一下数学的几个模板。
总感觉数学还是要多复习,不然很容易忘记。

简单数论

\(O(\sqrt{n})\)判定质数
不用多讲。
Miller-Rabin算法

bool mr(ll x,ll b)
{
    if(qp(b,x-1,x)!=1)
        return false;
    ll k=x-1;   
    while((k&1)==0)
    {
        k>>=1;
        ll d=qp(b,k,x);//快速幂,计算b^k mod x
        if(d!=1 && d!=x-1)
            return false;
        if(d==x-1)
            return true;
    }
    return true;
}
bool mr(ll x)
{
    if(x==46856248255981)
        return false;
    if(x==2||x==3||x==7||x==61||x==24251)
    return true;
    return mr(x,2)&&mr(x,3)&&mr(x,7)&&mr(x,61)&&mr(x,24251);
}

对于一个数\(x\),我们选一个质数\(b\)作为基底,依次计算\(b^{x-1},b^{\frac{x-1}{2}},b^{\frac{x-1}{2^2}},\cdots\)。注意:

  • 在第一次计算时不考虑\(b^{x-1} \equiv x-1\)的情况。
  • 遇到\(b^{\frac{x-1}{2^k}} \equiv x - 1\)或者\(\frac{x-1}{2^k}\)为奇数的情况就直接判定正确。

注意\(5\)个素因子积:\(2,3,5,7,24251\)。那个很强的伪素数\(46856248255981\)我倒觉得真心没必要背。

约数\(O(\sqrt{N})\)筛法
又叫试除法。每次围绕\(\sqrt{N}\)对称地找到两个因子\(p,q\),把这两个因子加入约数列表中。注意当\(p^2=N\)时要特判一下,不然会加多了。

倍数法
和线性筛的代码比较类似,适合一次筛多个质数的问题。

gcd
\[ \gcd(a,b) = \gcd(b, a \mod b) \]

埃式筛
比较好理解。

线性筛

for(rg int i = 1; i <= N; ++ i)
{
    if(v[i] < 0)
    {
        v[i] = i;
        prime[++ pn] = i;
    }
    for(rg int j = 1; j <= pn; ++ j)
    {
        if(prime[j] * i > N || prime[j] > v[i])
            break;
        v[i * prime[j]] = prime[j];
    }
}

暂时还没有试过这个代码。从理论上来说应该是没错的。

进阶数论

冷知识
设一个数的分解式:
\[ N = \prod_{i = 1}^{k}p_i^{\alpha_i} \]
我们可以把指数提取出来:
\[ N = (\alpha_1,\alpha_2,\cdots) \]
它可以帮助我们从全新的角度观察数论。其实说白了就是把数论中的运算变成对数形式。

举个例子,求两个数的\(\gcd\):
\[ \gcd((\alpha_i),(\beta_i)) = (\min\{\alpha_i,\beta_i\}) \]

这样我们就可以把方程\(\gcd(x,a)=b\)变成若干个整数不等式了。

再举个例子,整除关系:
\[ (\alpha_i) \big| (\beta_i) \iff \alpha_i \leq \beta_i \]
这样就也把整除关系变成若干个不等式了。从本质上来说,\(<\mathbb{Z}^{*},|>\)本身就是一个偏序集,因此才会有这样的特性。

可以结合这道题看一下:CSP-S 2009 Hankson的趣味题

重要函数的计算与预处理
欧拉函数\(\phi(n)\)

int phi(int n)
{
    int ret = n;
    for(rg int i = 2; i * i <= n; ++ i)
        if(n % i == 0)
        {
            ret = ret / i * (i - 1);
            while(n % i == 0)
                n /= i;
        }
    if(n > 1)
        ret = ret / n * (n - 1);//n是质数
    return ret;
}

这个是直接根据定义式计算的。
它的预处理版本:

void init()
{
    for(rg int i = 2; i <= n; ++ i)
        phi[i] = i;
    for(rg int i = 2; i <= n; ++ i)
        if(phi[i] == i)
            for(int j = i; j <= n; j += i)
                phi[j] = phi[j] / i * (i - 1);
}

当然,还有基于线性筛的版本:

void init()
{
    for(rg int i = 2; i <= N; ++ i)
    {
        if(v[i] == 0)
        {v[i] = i, prime[++ pn] = i, phi[i] = i - 1;}
        for(rg int j = 1; j <= pn; ++ j)
        {
            if(prime[j] * i > N || prime[j] > v[i])
                break;
            v[i * prime[j]] = prime[j];
            phi[i * prime[j]] = phi[i] * (i % prime[j] == 0 ? prime[j] : prime[j] - 1);
        }
    }
}

这里用到了\(\phi(i)\)的两个性质,这里也不多赘述。

莫比乌斯函数\(\mu(n)\)

for(rg int i = 1; i <= N; ++ i)
    mu[i] = 1, v[i] = 0;
for(rg int i = 2; i <= N; ++ i)
{
    if(v[i])
        continue;
    mu[i] = -1;
    for(rg int j = (i << 1); j <= N; j += i)
    {
        v[j] = 1;
        if((j / i) % i == 0)
            mu[j] = 0;
        else
            mu[j] *= -1;
    }
}

顺带一提,学长zsy讲过一个非常有趣的理解方式。这里不妨粘贴之。


我们都知道莫比乌斯反演:(简便起见,这里直接写成卷积形式)
\[ \begin{aligned} f = I * g\\ g = \mu * f \end{aligned} \]
从之前的素因子式\(N=(\alpha_i)\)的角度理解,\((\alpha_i)\)其实是一个高维向量,而\(g(\alpha_i)\)就是这个向量的点权,\(f(\alpha_i)\)就是\(g\)的前缀和。联想到二维前缀和\(a_i = S_i - S_{i-1}\),莫比乌斯反演事实上就是对于高维前缀和\(f(\alpha_i)\)的差分变换。

实际上,我之前补充过一个冷知识:广义的莫比乌斯反演。对于偏序关系\(\preceq\),定义它的莫比乌斯函数:
\[ \mu(x,y) = \begin{cases} 1 & x = y\\ -\sum_{x \preceq u \prec y} \mu(x,u) & x \prec y\\ 0 & \text{else} \end{cases} \]
则有:
\[ \begin{aligned} f(x) &= \sum_{0 \preceq u \preceq x} g(u)\\ g(x) &= \sum_{0 \preceq u \preceq x} \mu(u,x)f(u) \end{aligned} \]
这实际上说明了莫比乌斯反演实质上就是一个局部有限偏序集上的高维前缀和差分变换。这也进一步说明了莫比乌斯函数在容斥原理和数论上的链接作用。


模算术

正如你所见,\(a \mod b\)这个函数在设计时出现了一些问题,使得两个字母之间间隔太大。因此,下文中均用C++写法\(a\% b\)代表\(a\mod b\)。同理,为了方便书写,我们统一用\(a/b\)代表\(\lfloor\frac{a}{b}\rfloor\)

欧拉定理
若正整数\(a,p\)互质,则:
\[ a^{\phi(p)} \equiv 1 \pmod{p} \]
对于任意正整数\(b\),还有:
\[ a^b \equiv a^{b \% \phi(p)}\pmod{p} \]
如果\(a\)\(p\)不一定互质,则应该使用下面这个公式:
\[ a^{b} \equiv \begin{cases} a^{b\%{\phi(p)}+\phi(p)} & b > \phi(p)\\ a^b & b \leq \phi(p) \end{cases} \pmod{p} \]
证明均略去。

裴蜀定理和exgcd
对于整数方程\(ax+by=c\),存在整数解\((x,y)\)的充要条件是\(\gcd(a,b)|c\)。它的另一个版本是\(ax+by = \gcd(a,b)\)一定有整数解。
它的证明是基于\(\gcd(a,b)=\gcd(b,a\%b)\)的。在递归终点\(b=0\)时,显然有\(x=1,y=0\)。否则,
根据数学归纳法,假设下面这个方程成立:
\[ bx+(a\%b)y = \gcd(b,a\%b) \]
根据模数的另一种定义:
\[ a\%b = a - b(a/b) \]
我们可以得到:
\[ bx+(a-b(a/b))y = \gcd(b,a\%b)=\gcd(a,b) \]
整理得到:
\[ ay + b(x-(a/b)y) = \gcd(a,b) \]
因此,我们令\(x'=y, y'=(x-(b/a)y)\),就可以得到 \(ax'+by'=\gcd(a,b)\)了。
在程序中可以这样简写:

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

一次同余方程FCE
一般形式为\(ax \equiv b \pmod{p}\)。可以转换成\(ax + py = b\)的形式,然后用exgcd解出\((x,y)\)
假设\(\gcd(a,p)\not | b\),那么方程无解。
注意一下得到特解\(x_0\)后,方程的通解\(x \equiv x_0 \pmod{\frac{p}{\gcd(a,p)}}\)

逆元
\(a\)在模\(p\)意义下的逆元为\(a^{-1}\),则:\(a\cdot a^{-1} \equiv 1 \pmod{p}\)。这是一个FCE,直接求解就可以了。

值得注意的是逆元的几种非常特殊的求法:

  1. 如果\(p\)是质数,那么\(a\)的逆元就是\(a^{p-2}\pmod{p}\)

  2. 线性求逆元:\(i^{-1} \equiv (p-p/i)\times (p \% i) \pmod{p}\)

  3. 线性求阶乘逆元:\(\frac{1}{(i+1)!} \times (i+1) \equiv \frac{1}{i!} \pmod{p}\)

稍微注意一下,在用阶乘求逆元时可能会出现\(p | i!\)的情况,此时阶乘算出来都是\(0\)

中国剩余定理
设方程组:
\[ \begin{cases} x \equiv a_1 \pmod{p_1}\\ x \equiv a_2 \pmod{p_2}\\ \vdots\\ x \equiv a_k \pmod{p_k} \end{cases} \]
\(p_1,p_2,\cdots,p_k\)两两互质时,设\(P=\prod_{i=1}^{k}p_i\),则方程一定有整数解:
\[ x \equiv \sum_{i = 1}^{k}a_i(\frac{P}{p_i})(\frac{P}{p_i})^{-1} \pmod{P} \]
其中\((\frac{P}{p_i})^{-1}(\frac{P}{p_i})\equiv 1 \pmod{p_i}\)

假设\(p_1,p_2,\cdots,p_k\)不满足两两互质,那么应该用扩展版本。

\(x_{k-1}\)是通过EXCRT计算出的前\(k-1\)个方程的特解,而\(P_{k-1} = \prod_{i=1}^{k-1}p_i\)。为了让它满足第\(k\)个方程,有:
\[ x_{k-1} + \lambda P_{k-1} \equiv a_k \pmod{p_k} \]
已知\(x_{k-1},P_{k-1},a_k,p_k\),这个方程就是一个关于\(\lambda\)的FCE,可以直接计算。当然,如果它无解,那整个方程组就都无解了。新的特解\(x_k=x_{k-1}+\lambda P_{k-1}\)通过加上或减去\(P_k\)就是前\(k\)个方程的特解。
另外有个小技巧:求乘积\(P_k\)可以改为求前\(k\)个模数的\(\operatorname{lcm}\)。粘贴核心代码:

int main()
{
    n=qr(1);
    RP(i,1,n)
        m[i]=qr(1ll),a[i]=qr(1ll);//x = a[i] (mod m[i])
    
    ans=0,M=1;//M = prod
    RP(i,1,n)
    {
        ll p=FCE(M,a[i]-ans,m[i]);
        ans=ans+p*M;
        M*=m[i]/gcd(M,m[i]);
        ans=((ans%M)+M)%M;
    }
    printf("%lld",((ans%M)+M)%M);
    
    return 0;
}

高次同余方程(指数型)
求解形如\(a^{x}\equiv b \pmod{p}\)类型的,关于\(x\)的方程。其中有\(a,p\)互质,\(x\)非负。
这里没有一般的解析方法。但是由于\(a,p\)互质,我们可以自由计算逆元,进行和\(a\)有关的乘除法。
\(x = \epsilon t - \eta\),其中\(t=\lceil\sqrt{p}\rceil\)\(0\leq \eta < t\)。事先声明,这样的记法其实有点“混用”的意味,但事实上相比普通的拉丁字母,它更有助于记忆理解。
方程变成了:
\[ a^{\epsilon t - \eta}\equiv b \pmod{p} \]
即:
\[ a^{\epsilon t} \equiv b\cdot a^{\eta} \pmod{p} \]
对于所有的\(b\cdot a^{\eta},0 \leq \eta < t\),我们将它插入一个Hash表中。枚举\(\epsilon\)的所有取值\(0,1,\cdots,t\),依次判断在Hash表中是否有对应的\(b\cdot a^{\eta}\),然后拼凑得到答案。
这种方法叫做Baby step,Giant step。是一种类分块的优化枚举。

原文地址:https://www.cnblogs.com/LinearODE/p/11564834.html