zoukankan      html  css  js  c++  java
  • 「NOTE」数论小札

    因为有春节,所以实际上寒假比暑假长
    主要内容:原根 / 离散对数 / 二次剩余


    # 目录


    # 原根

    下面只讨论奇素数 (p) 的原根,在不特别强调时,运算在模 (p) 意义下进行。

    - 前置:阶

    对于与 (p) 互质的常数 (a),在满足 (a^xequiv1) 的所有正整数 (x) 中,最小的 (x) 称为 (a)(p) 的阶,记作 (ord(a))

    Theorem 1

    当且仅当 $ord(a)mid x$,有 $a^xequiv 1$。

    证明

    假设 $a^xequiv 1$ 且 $a^yequiv 1$,则有 $a^{x+y}equiv 1$ 且 $a^{x-y}equiv 1$。

    辗转相除法可得 $a^{(x,y)}equiv1$。

    $(x,y)le min{x,y}$,又有 $ord(a)$ 是满足 $a^xequiv1$ 的最小 $x$,所以 $ord(d)mid x$。

    Theorem 2

    若 $kmid ord(a)$,则 $ord(a^k)=frac{ord(a)}k$

    证明

    显然有 $frac{ord(a)}k$ 是 $(a^k)^xequiv 1$ 的解(代入方程、结合 $ord(a)$ 定义即可证明),只需证明 $frac{ord(a)}k$ 是最小解。

    假如存在 $x_0< frac{ord(a)}k$ 也是方程的解,则 $kx_0$ 是 $a^xequiv1$ 的解;而 $kx_0< ord(a)$,与 $ord(a)$ 定义矛盾。

    根据 Theorem 1 有推论 (ord(a)mid (p-1)),证明即费马小定理。

    - 原根

    若在模 (p) 意义下,(x^0,x^1,cdots,x^{varphi(p)-1}) 互不相同,也即 (ord(x)=varphi(p)),则称 (x)(p) 的原根,记为 (xi)

    实际上只有形如 (p^k,2p^k) 的数以及 (2,4) 有原根(虽然我不会证)。

    而根据原根的定义,模 (p) 意义下的任意元素都可以表示为 (xi^k)原根的一个重要作用)。

    Theorem 3

    设 $xi$ 是质数 $p$ 的一个原根,则 $xi^k$ 是原根当且仅当 $(k,p-1)=1$。

    由于任意元素都可以用 (xi^k) 表示。

    假设 (xi^k)(p) 的原根,则必须 (ord(xi^k)=varphi(p)=p-1)。令 (h=(k,p-1))

    [(xi^k)^{frac{p-1}h}equiv(xi^{p-1})^{frac kh}equiv 1 ]

    则有 (ord(xi^k)midfrac{p-1}h)

    • (h eq 1),则显然 (xi^k) 不是 (p) 的原根;
    • (h=1),因为 ((xi^k)^{ord(xi^k)}equiv1),则有 (xi^{kcdot ord(xi^k)}equiv1)(p-1mid ord(xi^k)),因此 (ord(xi^k)=p-1),也即 (xi^k)(p) 的原根。

    根据 Theorem 3,可以知道 (p) 的原根恰有 (varphi(p-1)) 个。

    - 计算原根

    直接从小到大枚举 awa

    注意到 (p) 的原根有 (varphi(p-1)) 个,其实还是蛮多的,于是枚举不了多少个就可以找到一个最小的原根 (xi_0)

    然后再枚举 ((k,p-1)=1)(k),就可以找出剩下的原根 (xi_0^k) 了。(一般不会真的让你求所有原根)

    怎么验算数 (x) 是不是 (p) 的原根?

    • 先验算是否 (ord(x)mid varphi(p)),直接快速幂检验 (x^{varphi(p)}) 是否为 (1) 即可;
    • 再验算 (ord(x)) 是否恰好是 (varphi(p)):把 (p-1) 的所有质因子找出来记为 (P={p_1,p_2,dots,p_m}),若 (forall p_iin P,x^{frac{p-1}{p_i}} otequiv1),则 (ord(x)=varphi(p))(x) 是原根。

    - 参考实现1

    int nprm,nfc,nans;
    int prm[N],phi[N],vis[N],fc[N],ans[N];
    bool ifrt[N]; //ifrt[i]=true: i有原根
    
    void Init(){
        //线性筛筛素数和欧拉函数
        phi[1]=1;
        for(int i=2;i<N;i++){
            if(!vis[i]) prm[++nprm]=i,phi[i]=i-1,vis[i]=i;
            for(int j=1;j<=nprm && prm[j]*i<N;j++){
                vis[prm[j]*i]=prm[j];
                if(i%prm[j]==0) {phi[i*prm[j]]=phi[i]*prm[j];break;}
                phi[i*prm[j]]=phi[i]*(prm[j]-1);
            }
        }
        //2,4,prime^k,2*prime^k 才有原根
        ifrt[2]=ifrt[4]=true;
        for(int i=2;i<=nprm;i++){
            for(llong j=prm[i];j<N;j*=prm[i]) ifrt[j]=true;
            for(llong j=2*prm[i];j<N;j*=prm[i]) ifrt[j]=true;
        }
    }
    int GCD(cint a,cint b){return b? GCD(b,a%b):a;}
    int QPow(int a,int b,cint MOD){
        int r=1;
        while(b){
            if(b&1) r=1ll*r*a%MOD;
            a=1ll*a*a%MOD;
            b>>=1;
        }
        return r;
    }
    //把 p 的所有质因子存入 fc[1~nfc] 中
    void PrimeDivide(int p){
        int las=-1; nfc=0;
        while(p>1){
            if(las!=vis[p]) fc[++nfc]=vis[p];
            las=vis[p];
            p/=vis[p];
        }
    }
    bool Check(cint x,cint p){
        //先验算 x 是否满足 ord(x)|phi[p]
        if(QPow(x,phi[p],p)!=1) return false;
        //然后验算 ord(x) 是否是 phi[p]
        for(int i=1;i<=nfc;i++)
            if(QPow(x,phi[p]/fc[i],p)==1)
                return false;
        return true;
    }
    int MinRoot(cint p){ //找最小的原根
        PrimeDivide(phi[p]);
        for(int i=1;i<p;i++)
            if(Check(i,p))
                return i;
        return 0;
    }
    void AllRoot(cint p){
        nans=0;
        int per=MinRoot(p),prod=1;
        for(int i=1;i<=phi[p];i++){ //拓展出所有的原根
            prod=(1ll*prod*per)%p;
            if(GCD(i,phi[p])==1) ans[++nans]=prod;
        }
    }
    

    # 离散对数

    - 引入问题

    求出下面方程的所有非负整数解:

    [a^xequiv bpmod p ]

    其中满足 ((a,p)=1);亦可扩展至 ((a,p) eq 1)

    - BSGS(大步小步)

    根据欧拉定理,当 ((a,p)=1) 时,有 (a^{varphi(p)}equiv1)。记原方程的最小正整数解为 (x_0),则原方程的解 (x) 可以表示为 (x=x_0+kvarphi(p)),于是只需要求 (x< varphi(p)) 的解。

    (q=lfloorsqrt p floor),将原方程的解 (x) 分解为 (x=nq-m),其中 (0le m< q)。易知 (n) 的数量级与 (q) 同阶。

    原方程可以写为:

    [egin{aligned} a^{nq-m}&equiv b\ a^{nq}&equiv ba^m end{aligned} ]

    由于 (n,m) 都是 (O(sqrt p)) 级别的,可以先 (O(sqrt p)) 将方程一边的所有取值都存进哈希表,然后另一边 (O(sqrt p)) 查询哈希表中是否有对应的值。

    例如,将 (m=0,1,cdots,q-1) 时的全部 (ba^m) 存入哈希表,然后枚举 (n),查找哈希表中是否存在 (a^{nq})

    - exBSGS

    之前提到离散对数可以扩展到 ((a,p) eq 1) 的情况,下面进行推导。

    同余方程 (a^xequiv bpmod p) 有如下性质:

    • ((a,p)=g)
    • (g otmid b),则无解,可以从同余方程的本质理解:(a^x=kp+b),则 (b=a^x-kp),若有解则 (b) 必然是 (g) 的倍数;
    • (gmid b),则等价为 (frac{a^x}gequivfrac bgpmod{frac{p}{g}})

    于是可以转化为

    [frac agcdot a^{x-1}equivfrac bgpmod{frac pg} ]

    方程左边相当于带了一个系数,注意到 (a) 仍有可能和 (frac pg) 不互质,只需要一直将方程同除以 ((a,p)),直到 (a,p) 互质即可。

    记每一次方程整体除的数是 (g_i),(G=prod g_i),最后方程的形式大概是

    [frac{a^k}Gcdot a^{x-k}equivfrac bGpmod{frac pG} ]

    求解方法还是和 BSGS 一样,用哈希表暴力储存方程一边的所有取值。

    注意到我们并不能保证不存在 (x< k) 的解。在转化过程中我们直接从 (a^x) 中提取出一个 (a) 而剩下 (a^{x-1}) 其实是不严谨的,因为转化时 (a,p) 不互质,则 (a^{-1}) 不存在。

    于是还要特判一下 (x<k) 有没有解。

    - 参考实现2

    /*
    Hs 是手写的一个哈希表,作用相当于 map
    */
    int GCD(int a,int b){return b? GCD(b,a%b):a;}
    int exBSGS(int vara,int varb,int varp){
        int total=1,add=0,mul=1,tmp=0,nowp=varp;
        while((tmp=GCD(vara,nowp))!=1)
            nowp/=tmp,total*=tmp,mul=1ll*(vara/tmp)*mul%varp,add++;
        tmp=1;
        for(int i=0;i<add;i++,tmp=1ll*tmp*vara%varp)
            if(tmp==varb)
                return i;
        if(varb%total) return -1;
        varb/=total;
        //mul*vara^(x-add)=varb (mod nowp)
        //mul*vara^y=varb (mod nowp)
        int varq=int(ceil(sqrt(nowp))+0.5),per=1;
        Hs.Clear();
        tmp=varb%nowp;
        for(int i=0;i<varq;i++,tmp=1ll*tmp*vara%nowp)
            Hs.Insert(tmp,i),per=1ll*per*vara%nowp;
        tmp=mul%nowp;
        for(int i=0,res;i<=varq;i++,tmp=1ll*tmp*per%nowp)
            if(~(res=Hs.Query(tmp)) && i*varq-res>=0)
                return i*varq-res+add;
        return -1;
    }
    //BSGS找最小的解
    void BSGS(int varp,int varn){
        varq=(int)(ceil(sqrt(varp))+0.5);
        int per=1;
        for(int i=0,tmp=varn;i<varq;i++,tmp=1ll*tmp*varb%varp)
            Hs.Insert(tmp,i),per=1ll*per*varb%varp;
        for(int i=0,tmp=1,res;i<=varq;i++,tmp=1ll*tmp*per%varp)
            if(~(res=Hs.Query(tmp)) && i*varq>=res){
                printf("%d
    ",i*varq-res);
                return;
            }
        printf("no solution
    ");
    }
    

    # 二次剩余

    - 引入问题

    求解 (x^2equiv bpmod p)(p) 是一个奇素数。

    若方程有解,则 (b) 称为 (p) 的二次剩余

    - 勒让德符号

    [left(frac{b}{p} ight)=egin{cases} 1& ext{b是p的二次剩余}\ -1& ext{b不是p的二次剩余}\ 0&bequiv0 end{cases} ]

    - 一些引理

    Lemma 1

    $$left(frac bp ight)equiv b^{frac{p-1}2}$$

    简单证明一下:

    • (bequiv0) 时显然;
    • (b)(p) 的二次剩余时,(exists xin[0,p),x^2equiv b)
      由费马小定理有 (x^{p-1}equiv1),又有 (p) 是奇素数;
      可以写成 ((x^2)^{frac{p-1}2}equiv1),则 (b^{frac{p-1}2}equiv1)
    • (b) 不是 (p) 的二次剩余时,由逆元的唯一性可得 (1,2,cdots,p-1) 可以按照“(i)(b imes i^{-1})(i) 的逆元)”的方式刚好划分为 (frac{p-1}2) 组,每组乘积为 (b)
      于是把 (1,2,cdots,p-1) 全部乘起来就可以得到 ((p-1)!=b^{frac{p-1}2}),又有 ((p-1)!equiv -1)(威尔逊定理,不会 QwQ),则 (b^{frac{p-1}2}equiv-1)

    Lemma 2

    $p$ 恰好有 $frac{p-1}2$ 个二次剩余。

    证明如下:设 (x,y) 满足 (x^2equiv y^2)(x< y)(x,yin[1,p-1])),移项可得:

    [egin{aligned} &(x-y)(x+y)equiv 0pmod p\Leftrightarrow &pmid(x-y)(x+y) end{aligned} ]

    又有 (p) 是素数且 (p otmid x-y),则 ((p,x-y)=1),进而可得 (pmid x+y)。在 ([1,p-1]) 中,这样的 ((x,y))(x^2equiv y^2) 一一对应

    所以 ([1,p-1])恰有 (frac{p-1}2) 个不同的 (x^2),即 (frac{p-1}2) 个二次剩余,剩下的 (frac{p-1}2) 个数则是非二次剩余。

    于是有结论:(p)二次剩余和非二次剩余一样多

    Lemma 3

    $$(a+b)^pequiv a^p+b^ppmod p$$

    直接二项式展开,(a^ib^{p-i}) 项的系数为 (C_p^i)

    因为 (p) 是质数,所以当 (i eq 0,i eq p) 时,(C_p^i=frac{p!}{i!(p-i)!}) 的分子是 (p) 的倍数而分母不是,所以 (pmid C_p^i)

    取模后只剩下首尾两项。

    补充一个讲课的dalao不会证所以我也不会证的定理:

    Theorem 4(二次互反律)

    $p,q$ 均为奇素数,则

    $$left(frac pq ight)cdotleft(frac qp ight)=(-1)^{frac{p-1}2 imes frac{q-1}2}$$

    - Cipolla

    如果我们知道 (b)(p) 的二次剩余,如何求解?

    可以先随机出一个 (a) 使得 (a^2-b) 不是 (n) 的二次剩余,用勒让德函数检验即可。由于 (p) 的二次剩余和非二次剩余是一样多的,所以可以较快地随机出这个 (a)

    可以知道不存在整数 (x) 使得 (x^2=a^2-b),于是我们“扩展数域”,让这样的 (x) 存在——定义单位 (omega^2equiv a^2-b),类似于复数域,域中的所有数可以表示为 (p+qomega) 的形式。

    Theorem 5

    $(a+omega)^{frac{p+1}2}$ 是原方程的一个根。

    直接代入证明:

    [egin{aligned} &(a+omega)^{p+1}\ =&(a+omega)(a+omega)^p\ =&(a+omega)(a^p+omega^p) end{aligned} ]

    (p) 是奇素数,(omega^2equiv a^2-b),则

    [omega^p=omegacdot(a^2-b)^{frac{p-1}2} ]

    因为 (a^2-b) 是非二次剩余,所以 ((a^2-b)^{frac{p-1}2}equiv-1)。又由费马小定理,(a^pequiv a),所以

    [a^p+omega^pequiv a-omega ]

    所以

    [egin{aligned} &Big[(a+omega)^{frac{p-1}2}Big]^2\ equiv&(a+omega)(a-omega)\ equiv&a^2-omega^2\ equiv&b end{aligned} ]

    于是 (x_1=(a+omega)^{frac{p-1}2})。由 Lemma 2,另一个根 (x_2) 满足 (pmid x_1+x_2),则 (x_2equiv p-x_1)

    因为 (p) 是奇素数,(x_1,x_2) 一定奇偶性不同,则有两个不等根。

    - 参考实现3

    > Link 洛谷 P5491

    /*Lucky_Glass*/
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    typedef long long llong;
    #define cint const int &
    
    int varp,conw;
    
    int Mul(cint a,cint b){return int(1ll*a*b%varp);}
    int Add(cint a,cint b){return a+b>=varp? a+b-varp:a+b;}
    int Sub(cint a,cint b){return a-b<0? a-b+varp:a-b;}
    int Pow(cint a,cint b){return b? Mul(Pow(Mul(a,a),b>>1),(b&1)? a:1):1;}
    struct COMPLEX{
    	int conr,coni;
    	COMPLEX(){}
    	COMPLEX(cint varr,cint vari):conr(varr),coni(vari){}
    	inline friend COMPLEX operator *(const COMPLEX &A,const COMPLEX &B){
    		return COMPLEX(
    			Add(Mul(A.conr,B.conr),Mul(Mul(A.coni,B.coni),conw)),
    			Add(Mul(A.conr,B.coni),Mul(A.coni,B.conr))
    		);
    	}
    	inline COMPLEX operator ^(int varb)const{
    		COMPLEX vara=(*this),res(1,0);
    		while(varb){
    			if(varb&1) res=res*vara;
    			vara=vara*vara;
    			varb>>=1;
    		}
    		return res;
    	}
    };
    
    //p is an odd prime
    int Solve(int varn){
    	varn%=varp;
    	if(Pow(varn,(varp-1)/2)==varp-1) return -1;
    	int vara,varw;
    	while(true){
    		vara=Mul(rand(),rand());
    		varw=(Sub(Mul(vara,vara),varn));
    		if(Pow(varw,(varp-1)/2)==varp-1) break;
    	}
    	conw=varw;
    	return (COMPLEX(vara,1)^((varp+1)/2)).conr;
    }
    int main(){
    	int cas;scanf("%d",&cas);
    	while(cas--){
    		int varn;scanf("%d%d",&varn,&varp);
    		if(!varn){printf("0
    ");continue;}
    		int ans1=Solve(varn),ans2;
    		if(~ans1){
    			ans2=varp-ans1;
    			if(ans1>ans2) swap(ans1,ans2);
    			printf("%d %d
    ",ans1,ans2);
    		}
    		else printf("Hola!
    ");
    	}
    	return 0;
    }
    

    THE END

    Thanks for reading!

    [egin{split} “ &夢のなかを歩きまわる\ &quadsmall{“ 能一直走在梦想中}\ &夜空を見上げて 星になるの\ &quadsmall{请仰望星空 会成为星星的}\ &祈りの消えた街 夢のなかで\ &quadsmall{在愿望消失的街道上 在梦中}\ &光の赤い夜寂れた心障\ &quadsmall{在泛着赤红色光芒的夜里 心被寂悸囚禁}\ &記憶の奥底で開いた壁 夢見て殺して ”\ &quadsmall{梦想着彻底冲破记忆深处的那副桎梏 ”}\ ——& ext{《余命3日少女(Cover)》By 米白} end{split} ]

    > Linked 余命3日少女-网易云

    欢迎转载٩(๑❛ᴗ❛๑)۶,请在转载文章末尾附上原博文网址~
  • 相关阅读:
    利用JSGrid创建甘特图
    Using the SharePoint 2010 Client Object Model_part_2
    Using the SharePoint 2010 Client Object Model_part_1
    如何利用Featue对特定的文档库或列表添加listviewtoolbar上的button
    SharePoint 2010 工作流解决方案:创建带有关联窗体和启动窗体的工作流
    Wireshark过滤器
    first blog
    DataAdapter.update如果处理自动增长列的数据
    ms sql 中关于spid=2的问题
    利用ADO.NET RowUpdating更新DataRow信息
  • 原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/14355598.html
Copyright © 2011-2022 走看看