zoukankan      html  css  js  c++  java
  • 数论总结(中) (中阶数论)

    #一.逆元的求解 ##1.引理: ###a.费马小定理 假如$a$是一个整数,$p$是一个素数,(gcd(a,p)=1),那么有: (a^{p-1}≡1(mod(p))) 费马小定理应用: [1]. 费马小定理降次:$p$是素数,(a),$p$互质,则$ab mod (p)={a{(b) mod (p-2)}} mod (p)$ [2]. 求质数逆元:若p是素数,(frac{1}{a}mod(p) = a^{p-2} mod(p)) 注意:不代表$ax≡1(mod (p))$中$x$的最小正整数值是$p-1$。 ###b.欧拉定理φ 若$n,a$为正整数,且$n,a$互质,即$gcd(n,a)=1$,则 (a^{φ(n)}≡1(mod (n))) 费马小定理是欧拉定理的特殊情况,因为当$n$为素数时,(φ(n)=n-1) 扩展欧拉定理降次: (1)当$n>1$,$(a,n)=1$时, (a^b \% n=a^{b \% φ(n)} \% n) (2)当$bleq φ(n)$时,直接计算即可。 (3)当$b > φ(n)$时 , (a^b \% n = a^{b\% φ(n) + φ(n)} \% n) 我是不会告诉你这个非常重要的。 ###欧拉定理应用2: 求解:(a^x≡1 (mod (Q))) , (x>0) 那么$x$的最小解一定是$φ(Q)$的约数 由同余的同幂性可以得到$x$的解集为 $φ(Q)$的 约数与倍数。 ###欧拉定理的套路技巧 (ORZ) $ laofu$队爷 , 这都能自己 (yy) 出来。 我们假设要求多次方,形如$a^{b^{cd}} %p$这种鬼东西。 那么化式子: (a^{b^c }\%p = a^{b^c \% ( varphi(p)+[?] )}\%varphi(p)) 然后$c$是对$varphi(varphi(p))$取模,以此类推递归下去。 那么那个$[?]$我们需要每次都判断一下真实$b$值与$varphi(p)$的大小关系。 这样非常不好做,这里队爷们发明了一种非常神奇的做法。 假设我们递归到了求$bc % ( varphi(p)+[?] )$ , 那么代码如下:

    ll Mul(ll x1,ll x2,ll mod)[
        return (x1*x2 < mod) ? (x1*x2) : (x1*x2%mod+mod);
    }
    ll Pow(ll bs,ll js,ll mod){
        ll S = 1, T = bs;
        while(js){
            if(js&1)S = Mul(S,T,mod);
            T = Mul(T,T,mod);
            js >>= 1;
        }return S;
    }
    ll Calc(int b,int phi(p)){ll js = Calc(c,phi(phi(p))); return Pow( b,js,phi(p) );}
    

    其中变量名是递归求解的,上面的$b,c,phi(p)$是表示当前算到$b^c % ( varphi(p)+[?] )$。 这样做的好处就是不用考虑$b$与$varphi(p)$的大小关系对求值的影响了。 其实非常简单,观察一下我们刚才重载的乘法函数 (Mul) , 如果$x_1*x_2$都已经大于$varphi(p)(了,那么后面运算肯定大于)varphi(p)$ , 所以之后的运算中始终要加上一个$varphi(p)$。 ##2.单个逆元的计算 给定$a,n$$(n>1)$,$gcd(a,n)=1$计算$a$对模$n$的乘法逆元$x$.

    ###方法1:用前面讲的Exgcd解方程$ax≡1(mod (n))$ 即$ax+ny=1$,得$x$的特解$x_0$,则$a^{-1} mod (n)=(x_0)mod (n)$,解唯一!

    ###方法2:利用欧拉定理$a^{φ(n)}$$≡$$1$$(mod (n))$ (a^{-1} mod (n)=(a^{φ(n)-1}mod (n))mod (n)) 计算$a^{φ(n)-1}mod (n)$调用快速幂$pow(a,φ(n)-1)$来计算,

    ###方法3:当n为质数时,变为费马小定理降次: (a^{-1} mod (n)=(a^{n-2}mod (n))mod (n)) ##3.线性求解逆元 代码如下:

    inv[1]=1;
    for(int i=2;i<=p;i++)
        inv[i]=(M - M/i) * inv[M%i] %M;
    

    证明: #二.中国剩余定理(CRT)。 ##1.中国剩余定理

    ##2.扩展中国剩余定理 同余方程中的$n_1,n_2,...,n_k$不两两互质怎么办? 用扩展中国剩余定理: 两个方程$x≡b_1 (mod (a_1))$与$x≡b_2 (mod (a_2))$ 那么有$x = a_1x_1 + b_1 = a_2x_2 + b_2$; 我们要最小化$x$,所以最小化$x_1$。 然后回代得到$x$的特解$x'$,当然$x'$也是最小正整数解。 所以$x$的通解为:(x = x' + k*lcm(a_1,a_2))。 所以得到了一个新的方程: (x ≡ x' (mod (lcm(a1,a2)))) ; 所以每次把两个方程合并成$x≡x' (mod (lcm(a1,a2)))$, 然后用该方程与接下来的一个联立,依次求解即可得出结果。 最后得到一个总方程:(x ≡ C (mod (P))) 解这个方程即可得到整个方程组的根。 #三.MR素数判断 ##1.二次勘探定理 若$p$是质数 、 (x^2 ≡ 1(mod(p))), 那么$x$的解为 (x=1)(x=n-1) ##2.MR素数判断 还记得费马小定理吗?不记得就在上面 先把模数$P$变为$m*2^k$,然后依次使用二次勘探定理即可。 具体太长了,给一篇博客:戳我! 核心代码(检测$n$的素性):

    //本代码只测试15次,次数越高,准确率越高。
    for (int i = 1; i <= 15; i++){  
        ll a = rand() % (n - 1) + 1;  
        ll x = pow(a, m);     //计算a^m %n
        ll y;  
        for (int j = 1; j <= k; j++){  
            y = mul(x, x);       //计算x*x %n ,使用龟速乘  
            if (y == 1 && x != 1 && x != n - 1)return false;  
            x = y;  
        }  
        if (y != 1)return false;  
    }  
    

    实在看不懂背背板子就行了。 #四.矩阵乘法 矩阵快速幂要用,这里不讲了给一个板子:

    struct matrix{
        int a[5][5];
        matrix(){ memset(a,0,sizeof(a)); }
        int *operator [](int x){ return a[x]; }
        matrix operator*(matrix &b){
            matrix c; 
            for(int i = 0 ; i < n ; i ++)
    			for(int j = 0 ; j < n ; j ++)
    				for(int k=0 ; k < n ; k ++)
    					c[i][j]=(c[i][j]+1ll*a[i][k]*b[k][j])%mod;
            return c;
        } 
    }T,S;
    
    

    #四.高斯消元 ##1.高斯消元求解方程组(Gauss)。 百度一下,你就知道,这里只给代码实现。 注:mp[n][n+1]为方程矩阵,其中mp[i][n+1]为第i个方程的常数项。 当然最后答案也存在mp[i][n+1]中,代码中输出$No$ $Sol$是指有无数多个解。 具体看$Luogu$的板子题:戳这里! 补:省选中的高斯消元模板题:戳这里!

    int main(){
        cin >> n;
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n + 1; j ++)
                cin >> mp[i][j];   
        for(int j = 1; j <= n; j ++){
            int rgt = 0;
            for(int i = j; i <= n; i ++)
                if(mp[i][j]){rgt = i; break;}
            if(!rgt)continue;
            if(rgt ^ j)swap(mp[rgt],mp[j]);
            if(mp[j][j] == 0){cout<<"No Solution"; return 0;}
            for(int i = j + 1; i <= n; i ++){
                double div = mp[i][j] / mp[j][j];
                for(int k = 1; k <= n + 1; k ++)mp[i][k] -= div*mp[j][k];
            }
        }
        for(int j = n; j >= 1; j --){
            mp[j][n+1] =  mp[j][n+1] / mp[j][j];
            for(int i = j-1; i >= 1; i --)mp[i][n+1] -= mp[j][n+1] * mp[i][j];
        }
        for(int i = 1; i <= n; i ++)printf("%.2lf
    " ,mp[i][n+1]);
        return 0;
    }
    

    ##2.高斯消元求解异或方程(Gauss_Xor) 其实差不多,具体流程如下: (1)找到当前列系数不为0的一行,并将其交换到当前行。 (2)用当前行去消 当前列系数不为0的所有行 , 只是运算为异或罢了。 (3)消到最后只剩下一个元,解出来。 (4)向上一路回带即可(具体怎么回带自己手玩一把即可)。 注意一下: 假设消除到某一行 (1)若所有系数都为0,而常数项不为0,那么则无解。 (2)若所有系数都为0,且常数项为0,那么则有无穷多个解。 然后常用的搭配为bitset(一个STL),真的是卡常数神器。 具体的使用方法给一篇博客:http://blog.csdn.net/qll125596718/article/details/6901935 一道板子题:[SDOI2010]外星千足虫 然后这题有点难度:[HNOI2011]XOR和路径 具体代码:(以上面的例题为例)

    #include<bits/stdc++.h>
    #define ll long long
    #define IL inline
    #define RG register
    using namespace std;
    IL int gi(){
        RG int date = 0, m = 1;  RG char ch = 0;
        while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
        if(ch == '-'){m = -1; ch = getchar();}
        while(ch>='0'&&ch<='9'){date=date*10+ch-'0';ch=getchar();}
        return date*m;
    }
    
    int n,m,Round;
    bitset<2050>mp[2050]; char s[2050][2050];
    
    
    IL bool Gauss_Xor(){
        for(RG int j = 1; j <= n; j ++){
            RG int rgtpos = 0;
            for(RG int i = j; i <= m; i ++)
                if(mp[i][j]){rgtpos = i; break;}
            //找到一个当前位不为0的方程....
            if(!mp[rgtpos][j])return false;   //无解       
            if(rgtpos != j)swap(mp[j] , mp[rgtpos]);
            Round = max(Round,rgtpos);
            for(RG int i = j+1; i <= m; i ++)
                if(mp[i][j])mp[i] ^= mp[j];
        }
        for(RG int j = n; j >= 1; j --){
            //mp[j][n+1] 此时即为解;
            for(RG int i = j-1; i >= 1; i --)
                mp[i][n+1] = mp[i][n+1] ^ (mp[i][j]*mp[j][n+1]);
        }
        return true;
    }
    
    int main(){
        n = gi(); m = gi();           //有m(m>=n)个方程,每个方程左边有n个系数.
        
        for(RG int i = 1; i <= m; i ++){
            scanf("%s",s[i]+1);
            for(RG int j = 1; j <= n; j ++)
                mp[i][j] = s[i][j] - '0';
            mp[i][n+1] = gi();
        }	
        Round = 0;
        if(!Gauss_Xor())printf("Cannot Determine");    //无解
        else{
            printf("%d
    ",Round) ;
            for(RG int i = 1; i <= n; i ++)
                puts(mp[i][n+1] ? "?y7M#" : "Earth");
        }return 0;
    }
    
    

    #五.组合数学。 ##1.数学表示: $C_mn$表示在$m$个中选$n$个的方案数。 (把$m$个无区别物品放到$n$个有区别篮子的方案数) $P_mn$表示在$m$个中选$n$个的排列数。 (把$n$个有区别物品放到$m$个有区别篮子的方案数) $S_mn$表示斯特林数(S(n,m))。 (把$n$个有区别物品放到$m$个无区别篮子,篮子不空的方案数) ##2.常用公式: (C_n^m = C_{n-1}^{m-1}+C_{n-1}^{m}) (prod_{n-m+1}^n = C_n^m*{m!}) (n^m = sum_{j=1}^{n}S^m_j*prod_{n-j+1}^n =sum_{j=1}^nS_j^m*{j!}C_n^j) (C_n^k = C_n^{n-k}) ((a+b)^n = sum_{k=0}^nC_n^ka^{n-k}b^k(二项式定理)) $$S_mn=S_*m+S_ $$ (C_n^{k+1} = C_n^k*frac{n-k}{k+1}) (S_m^n = 0(if(m>n))) (sum_{k=1}^n C_n^k*C_k^t = C_n^t * 2^{n-t}) (m!*S_m^n = sum_{i=0}^m (-1)^iC_m^i*(m-i)^n) 自己用组合意义推一下即可,都不是很难。 ##3.可重组合数学: ###a.可重排列 有$k$个元素,其中第$i$个元素有$n_i$个,求全排列数。 (P' = frac{({ Sigma_{i=1}^k n_i})!}{{n_1!}{n_2!}....{n_k!}}) 即先做全排列,然后给每个元素编号,具体见蓝书P104 ###b.可重组合 有$n$个元素,每个元素可以选无穷多个,一共选$k$个,求方案数。 (C' = C^{n-1}_{k-n+1} = C^{k}_{k-n+1}) 假设第$i$个元素选$x_i$个,那么原问题变为$x_1+x_2+.....+x_n=k$ 我们令$y_i = x_i + 1$,那么$y_1+y_2+y_3+....+y_n = k+n$ 此时$y>0$,即每个元素都要选。 所以等于在$k+n$个元素($k+n-1$个空位)间放$n-1$个隔板。

    #六.数论分块。 这其实是因题目而定的啦。这里主要是讲这种思路(套路)。 我们假设求解$sum_^n lfloorfrac floor$ 那么应该怎么在$sqrt$的时间里求出来? 观察到$lfloorfrac floor$的取值只有$sqrt$个。 所以可以类似分块算法那样搞一个数论分块。 定理:若有一个值$i$,那么数论分块中其同值上界为: (ceil = iggllfloor frac{n}{lfloorfrac{n}{i} floor} iggr floor) 即在$[i,ceil](这一段区间内,)lfloorfrac floor$的取值是一样的。所以就可以直接计算这一整块的贡献。 具体代码如下(以求$sum_^n lfloorfrac floor$为例):

    int l = 1 , r , ans = 0;
    while(l<=n){
        r = n/(n/l);    
        ans += (r-l+1)*(n/i);
        l = r + 1;
    }
    

    思考一下如果是二维怎么办,如求$sum_^{min(n,m)} lfloorfrac floorlfloorfrac floor$ 其实也非常简单啦,对于$i$,(j_1 = n/lfloorfrac{n}{i} floor) , (j_2 = m/lfloorfrac{m}{i} floor) , 那么$j = min(j_1,j_2)$。 所以每次都取$min$,然后不断跳即可。 数论分块真的非常重要,在后面的高级数论(如莫比乌斯反演)中会经常用到。

  • 相关阅读:
    windows系统使用sketch设计的设计稿
    移动端点击按钮复制链接
    设置display:inline-block 元素间隙
    修改url中参数值
    fiddler主要图标说明
    fiddler抓包工具
    数据库删除
    having的用法
    left join on和where
    Statement和PreparedStatement有什么区别?哪个效率高?
  • 原文地址:https://www.cnblogs.com/GuessYCB/p/8258373.html
Copyright © 2011-2022 走看看