浅析逆元、扩展欧几里得、类欧几里得和莫比乌斯反演(填坑ing)

逆元


一点没有多大用的东西

  • 在数论里面,我们不把倒数叫做倒数,而叫做逆元(纯属装13)

  • 逆元的作用很大,我们来看点easy的栗子


某些性质

( a + b ) a m o d p + b m o d p ( m o d p )

( a b ) a m o d p b m o d p ( m o d p )

( a × b ) a m o d p × b m o d p ( m o d p )

  • 前面三个都是对的(不需要证明~)

  • 但——

    ( a ÷ b ) m o d p ( ( a m o d p ) ÷ ( b m o d p ) ) m o d p

(÷是整除)

  • 随便举个例子就知道它不成立

随便搞一下

这是不是说我们对除法下的大数模操作就毫无办法了?naive

  • 为了方便,先来看看一些小学or初中级别的东东

  • 有条等式

    a x = 1

  • 明显 x = 1 a ,我再加一个条件

a x 1 ( m o d p )

  • 那么现在 x 不一定等于 1 a

  • 对于①,我们把 x 看成 1 a ,而②式加了一个同余 p

  • 这时我们不把 x 看成倒数,这时的x为a关于p的逆元

(注意,当且仅当 ( a , p ) = 1 且p为质数时,x才存在)


又是一个栗子

( 3 × 5 ) m o d 7 = 1
3 × 5 1 ( m o d 7 )

  • 在这里, 5 1 3 的作用是一样的(乘3再模7都是1),所以,5是3关于7的逆元

注意一点,除了 α = 5 还有其他整数 α 满足 3 α 1 ( m o d 7 ) ,如 19
连百度都说逆元唯一,但是逆元不是唯一的!!!(感谢同学指出)


逆元的卵用

  • 前面已经说了一些东东了

  • 相信逆元的定义很好懂

  • 我们不妨把 x 的逆元用 i n v ( x ) 来表示,那么对于除法——

    ( a ÷ b ) m o d p = ( a × i n v ( b ) ) m o d p = ( a m o d p × i n v ( b ) m o d p ) m o d p

难搞的除法瞬间变成了乘法,变水了很多


逆元这东东怎么求

way one

基础数论里面有两条著名的定理:欧拉定理费马小定理

  • 欧拉定理(当 ( a , n ) = 1 时成立)

  • 费马小定理(当 ( a , p ) = 1 且p为质数时成立)
    (我难道会告诉你我两个都不会证明吗……)

明显,费马小定理是可以从欧拉定理推过来的

当p为质数时 φ ( p ) = p 1 ,代入欧拉定理即费马小定理


  • 我们在谈论逆元,就把费马小定理两边同除以一个p

    a p 2 1 / a ( m o d p )

  • 注意到一点就是 1 a 是个小数,而等号左边是个整数,别忘了我们在说的是数论倒数

  • 想一想,我们发现应该把 1 a 写成inv(a)——

  • so——

i n v ( a ) a p 2 ( m o d p )

我们可以用快速幂来求a的逆元,时间复杂度 O ( l o g 2 p )


code

long long inv(long long a,long long p)
{
    long long t=1,b=p-2;
    while (b)
    {
        if ((b%1)==1)t=t*a%p;
        a=a*a%p;
        b/=2;
    }
    return t;
}

通常题目会让你求 10 9 + 7 取模,快到飞起
快速幂求逆元,对于大多题目,够了


way two

逆元还可以用扩展欧几里得来求


首先要知道贝祖定理,描述是

对于 a , b 0 a , b N ,必然存在整数x和y,使得 a x + b y = g c d ( a , b )

  • 比如我们现在要求 a 关于 p 的逆元(条件是 a p 互质且 p 是质数)

  • 代入贝祖定理,则

    a x + p y = 1
    (因为a和p互质所以 ( a , p ) = 1 )

  • 给上面那个式子做点操作,两边各模一个 p ,得到

    a x m o d p + p y m o d p = 1 m o d p
    a x m o d p = 1 m o d p
    a x 1 ( m o d p )

  • ∴x是a关于p的逆元(其实 y 也是 p 关于 a 的逆元,可证,同理)

这里用扩展欧几里得 e x g c d ( a , p , x , y ) ,求出的 x 即为 a 的逆元


code

void exgcd(long long a,long long b,long long &x,long long &y,long long &z)
{
    if (!b) 
    {
        z=a;
        x=1;
        y=0;
        return;
    }
    exgcd(b,a%b,y,x,z);
    y-=x*(a/b);
}
long long inv(long long a,long long b)
{
     long long x,y;
     exgcd(a,b,x,y);
     return (x%p+p)%p;
}

扩展欧几里得可谓比较牛,优点是用途多,能求gcd能求逆元能解线性方程
对于广大OIers是把利器,学会是必须的


way three

最后一种方法更简单更好打更好理解:递归


先来一发证明网上找的

x = p m o d a y = p ÷ a
( x + a y ) m o d p = p m o d p = 0
x m o d p = a y m o d p
x · i n v ( a ) m o d p = ( y ) m o d p
i n v ( a ) = ( p y ) · i n v ( x ) m o d p
i n v ( a ) = ( p p ÷ a ) · i n v ( p m o d a ) m o d p

  • 有了这个,我们就可以用递归来求 i n v ( a )

  • 递归出口 i n v ( 1 ) = 1 ,因为 1 的逆元就是 1


递归code

long long inv(long long a,long long p) //注意a要小于p,先把a%p一下 
{
    return a==1?1:(p-p/a)*inv(p%a,p)%p;
}

是不是短到爆炸?

  • 因为 p m o d a 后至多是 p 2 ,所以时间复杂度是 O ( l o g 2 p )

  • 这种方法还有一个好处,能用 O ( n ) 的时间预处理1~n的逆元

只不过把递归的式子改成递推而已


递推code

void init()
{
    inv[1]=1;
    for(int i=2;i<=n;i++)
    {
        inv[i]=(p-p/i)*1ll*inv[p%i]%p;
    }
}

个人感觉方法三在某些题目里更好用一些
易理解,码量低,时间复杂度也低,还能快速预处理
因吹斯听


:D

  • 逆元没得好说

  • 三种方法都是求所有逆元中小于 p 的那个

  • 其实都挺好用的

  • 虽说我倾向于第三种,这个东西多少随意

  • 扩展欧几里得是什么?顺便看看?


扩展欧几里得


先复习一下普通欧几里得算法吧

普通欧几里得算法又称辗转相除法,为 g c d ( a , b ) = g c d ( b , a m o d b )

  • 我TM傻到连证明也要CO了

  • 证明没什么必要图个开心罢了

证明一发?

  • 先设 a > b

  • a 可以表示成 a = k b + r a , b , k , r N , r < b ), r = a m o d b

  • d a b 的一个公约数,那么 d | a , d | b

  • r = a k b 两边各除以 d ,得到 r d = a d k b d = m

  • d | a , d | b m 为整数, d | r 也就是 d | a m o d b

  • d b a m o d b 的公因数

  • d b a m o d b 的公因数中的任意一个

  • d | b , d | a m o d b ,设 a m o d b = a k b ( k Z ) d | a k b

  • d | a d | a , d | b , d | a m o d b ,就是说 a , b , a m o d b 的所有公因数是一样的

  • 公因数相同,最大公因数不也就相同了? g c d ( a , b ) = g c d ( b , a m o d b )


code

long long gcd(long long x,long long y)
{
    return !y?x:gcd(y,x%y);
}
  • m o d 一次,大的那个数至多是原来一半

  • 所以时间复杂度 O ( l o g 2 n )

  • 貌似欧几里得平均迭代次数 ( 12 · l n 2 · l n N ) π 2 + 1.47 , N = m i n ( x , y ) 耶……

  • 随意……


还是要学扩欧的

不会

a x + b y = c ( a , b , c , x , y Z )

  • 扩欧就是已经知道 a , b , c ,求解能使该等式成立的一组 x , y

  • 明显的啊,想要有整数解的话,必须满足 g c d ( a , b ) | c

  • 不然两边各除以 g c d ( a , b ) ,等号左边是整数,而等号右边就是个分数了→_→您太强啦


不知道怎么求就右上角

  • a x + b y = g c d ( a , b )

  • b = 0 时,明显 g c d ( a , b ) = a , x = 1 , y = 0

  • 但当 a , b 0 时,设 a x 1 + b y 1 = g c d ( a , b ) , b x 2 + ( a m o d b ) y 2 = g c d ( b , a m o d b )

  • g c d ( a , b ) = g c d ( b , a m o d b ) , a x 1 + b y 1 = b x 2 + ( a m o d b ) y 2

  • a x 1 + b y 1 = b x 2 + ( a a b · b ) y 2 = a y 2 + b x 2 + a b · b y 2

  • 根据某恒等定理 x 1 = y 2 , y 1 = x 2 a b · y 2

  • 所以 x 1 y 1 的值是由 x 2 y 2 来决定的

  • x 2 y 2 的值通过递归得到

  • 如此完美


code

long long exgcd(long long a,long long b,long long &x,long long &y)
{
    if (!b) 
    {
        x=1,y=0;
        return a;
    }
    long long ans=exgcd(b,a%b,x,y);
    int temp=x;
    x=y;
    y=temp-a/b*y;
    return ans;
}

其实差不多就是从上面CO过来的

  • 直接调用exgcd(a,b,x,y),结果是返回 g c d ( a , b )

  • 同时求出来的 x y 能干嘛呢?


扩欧?有什么用?

扩欧一共有三种用途


purpose one


purpose two


purpose three


类欧几里得


随意随意

  • 类欧几里得也是一种算法

  • 它的样子很欧几里得算法,所以叫做类欧

  • 它可以解决 O ( n ) 时间的问题,然而它的时间复杂度和欧几里得是一样的,都是 O ( l o g 2 n )

  • 下面进入类欧的奇幻♂世界


三个函数和一个m

f ( a , b , c , n ) = i = 0 n a i + b c

g ( a , b , c , n ) = i = 0 n i a i + b c

h ( a , b , c , n ) = i = 0 n a i + b c 2

m = a n + b c

  • 看官随便看看

  • 反正类欧可以把这三个明显 O ( n ) 的东东用 O ( l o g 2 n ) 搞掉

您也知道鄙人数论不好,所以这坑要多久填完我不好说

下面的除法都看成整除,不再添加 了,每个除法都打一次太eggsache了


求f

f ( a , b , c , n ) = i = 0 n a i + b c

  • 分类讨论!

a = 0

f ( a , b , c , n ) = i = 0 n a i + b c = i = 0 n 0 · i + b c = i = 0 n b c
= b c ( n + 1 )

a c b c

f ( a , b , c , n ) = i = 0 n a i + b c = i = 0 n ( a c i + b c + a m o d c · i + b m o d c c )
= a c n ( n + 1 ) 2 + b c ( n + 1 ) + f ( a m o d c , b m o d c , c , n )

a c b c

f ( a , b , c , n ) = i = 0 n a i + b c =


莫比乌斯反演


都说了定义没用的还不信

莫比乌斯反演是数论数学中很重要的内容
在许多情况下能够简化运算,可以用于解决很多组合数学的问题
没个卵用

  • 反正是个很奇妙的东西

  • 并没有在网上找到莫比乌斯反演的详细解释

  • 不墨迹了


懵逼乌斯繁衍

  • 定义

    f ( n ) = d | n g ( d )

  • 莫比乌斯反演就是在已知 f 的情况下反演求 g

  • 我们可以试着用 f 来表示 g ,则有

    g ( n ) = d | n μ ( n d ) × f ( d ) = d | n μ ( d ) × f ( n d )

  • 我不会证明

  • μ 是什么呢?


关于 μ

  • 不必知道 μ 是什么东西
    这只是我们自己YY出来的、觉得这里应该有的一个函数

  • 只需要知道 μ 就是莫比乌斯函数

  • 推一波 f ( n ) = d | n g ( d ) 可以发现一些东东
    白内障看不清就放大

  • 看得出 g ( n ) = d | n ? ? ? ( n d ) × f ( d )

  • 其实 ? ? ? 就是 μ ( n d )

  • 容易发现 μ 只能取 { 1 , 0 , 1 }

  • 更准确地给 μ 来取值,那么

    (1) μ ( n ) = { 1 n = 1 ( 1 ) k n = i = 1 k p k 0 a 2 | n , a N , a > 1

  • 上面 p 为互质的质数,这个应该看得懂

  • 暴力求 μ 肯定很慢,怎么快速求 μ 呢?


注意 μ 的性质

μ 的性质一

k | n μ ( k ) = [ n = 1 ]

不会证
这个东西对接下来推式子有很大的帮助
必须记住


μ

μ 性质二:是积性函数,但不是完全积性

  • 线性筛!

  • 思考对于数 i ,若 i 为质数则 μ ( i ) = 1

  • i m o d p [ j ] == 0 ,则 i p [ j ] 有平方因子,所以 μ ( i p [ j ] ) = 0

  • 否则 μ ( i p [ j ] ) = μ ( i )


code

线筛里面 ϕ , μ 都可以求出来

void init() 
{
    memset(bz,1,sizeof(bz));
    mu[1]=phi[1]=1,tot=0;
    for (int i=2;i<MAXN;i++) 
    {
        if (bz[i])p[tot++]=i,phi[i]=i-1,mu[i]=-1;
        for (int j=0;j<tot && i*p[j]<=MAXN;j++) 
        {
            bz[i*p[j]]=0;
            if (i%p[j]==0) 
            {
                mu[i*p[j]]=0;
                phi[i*p[j]]=phi[i]*p[j];
                break;
            } 
            mu[i*p[j]]=-mu[i],phi[i*p[j]]=phi[i]*(p[j]-1);
        }
    }
    for (int i=1;i<MAXN;i++)pre[i]=pre[i-1]+mu[i];//μ的前缀和,会有用的
}

这种数学的东东还是要草稿纸什么才行
直接上反演公式套路

默认下面的 n 小于 m ,除都是整除


繁衍套路1

i = 1 n j = 1 m [ g c d ( i , j ) = 1 ]


推倒

  • 想到性质一也就是 k | n μ ( k ) = [ n = 1 ]

  • 把它放进上面的式子里面

= i = 1 n j = 1 m k | g c d ( i , j ) μ ( k )

  • 想一下 μ ( k ) 出现了多少次呢? n k m k 次!!

  • 所以

= k = 1 n μ ( k ) n k m k

  • 为了美观把 k 换成 i ,下面不再强调了,最后

= i = 1 n μ ( i ) n i m i

  • 暴力只能 O ( n ) 求了,但是我们可以分块做到根号复杂度

  • 先看套路2


繁衍套路2

i = 1 n j = 1 m [ g c d ( i , j ) = d ]


推倒

  • 和套路1差不多

  • 根据容斥,同时除上一个 d

= i = 1 n d j = 1 m d [ g c d ( i , j ) = 1 ]

= i = 1 n j = 1 m k | g c d ( i , j ) μ ( k )

  • 然后根据我们已经推出来了的

= i = 1 n d μ ( i ) n i d m i d

  • 暴力时间复杂度 O ( n )

  • 开始分块

  • 我们容易知道的是, n d 最多只有 n 个取值(不会证)

  • 同理 m d 最多也只有 m 个取值,所以 n i d m i d 同时不变的段数有 n + m

  • 先看一幅图网上盗的

    这里 n = 32 , m = 40 , k = 2 红色为 n k d m k d 不变的整段

  • 看懂了么?

  • 既然这些段 n k d , m k d 都不变,意味什么?

  • 我们取 μ 的前缀和,然后拿每一段的最后一位 μ 的前缀和乘上 n k d m k d 不就是这一段的答案和么?

  • 分块时间复杂度 O ( n )


code

int get(int n,int m,int d) 
{
    if (n>m)swap(n,m);
    int ans=0;
    n/=d,m/=d;
    for (int i=1,last=1;i<=n;i=last+1)
    {
        last=min(n/(n/i),m/(m/i));
        ans+=(pre[last]-pre[i-1])*(n/i)*(m/i);
    } 
    return ans;
}

例题1:【BZOJ2301】 [HAOI2011]Problem b

problem


analysis

  • 反演例题

  • 由容斥可得, a n s = a n s ( b , d ) a n s ( a 1 , d ) a n s ( b , c 1 ) + a n s ( a 1 , c 1 )

  • 单分块还不会?上面刚刚推出来的

  • 于是每一个分块都是 O ( n + m ) 的时间,总 O ( n ( n + m ) ) 时间复杂度


code

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define MAXN 10000001 

using namespace std;

bool bz[MAXN];
int p[MAXN],phi[MAXN],mu[MAXN],pre[MAXN];
int n,a,b,c,d,k,tot;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0' || '9'<ch)
    {
        if (ch=='-')f=-1;
        ch=getchar();   
    }
    while ('0'<=ch && ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}

void init() 
{
    memset(bz,1,sizeof(bz));
    mu[1]=phi[1]=1,tot=0;
    for (int i=2;i<MAXN;i++) 
    {
        if (bz[i])p[tot++]=i,phi[i]=i-1,mu[i]=-1;
        for (int j=0;j<tot && i*p[j]<=MAXN;j++) 
        {
            bz[i*p[j]]=0;
            if (i%p[j]==0) 
            {
                mu[i*p[j]]=0;
                phi[i*p[j]]=phi[i]*p[j];
                break;
            } 
            mu[i*p[j]]=-mu[i],phi[i*p[j]]=phi[i]*(p[j]-1);
        }
    }
    for (int i=1;i<MAXN;i++)pre[i]=pre[i-1]+mu[i];
}

int get(int n,int m,int d) 
{
    if (n>m)swap(n,m);
    int ans=0;
    n/=d,m/=d;
    for (int i=1,last=1;i<=n;i=last+1)
    {
        last=min(n/(n/i),m/(m/i));
        ans+=(pre[last]-pre[i-1])*(n/i)*(m/i);
    } 
    return ans;
}

int main()
{
    //freopen("readin.txt","r",stdin); 
    init();
    n=read();
    while (n--)
    {
        a=read(),b=read(),c=read(),d=read(),k=read();
        printf("%d\n",get(b,d,k)-get(a-1,d,k)-get(b,c-1,k)+get(a-1,c-1,k));
    }
    return 0;
}

繁衍套路3

i = 1 n j = 1 m g c d ( i , j )


推倒

  • 枚举约数 d

= d = 1 n d i = 1 n j = 1 m [ g c d ( i , j ) = d ]

= d = 1 n d i = 1 n d j = 1 m d [ g c d ( i , j ) = 1 ]

  • 还是再加一个 进去再变形

= d = 1 n d i = 1 n d j = 1 m d k | g c d ( i , j ) μ ( k )

= d = 1 n d i = 1 n d μ ( i ) n i d m i d


猜你喜欢

转载自blog.csdn.net/enjoy_pascal/article/details/77417556