Loading

数论总结

cf906D-exgcd

题面:

\[给定一个数列w_1,w_2,...,w_n和模数p,每次询问一个区间[l,r],求w_l^{w_{l+1}^{w_{l+2}^{{...}^{w_r}}}} mod p \]

\[w_1,w_2,...,w_n \]

\[w_l^{w_{l+1}^{w_{l+2}^{{...}^{w_r}}}} mod p \]

使用扩展欧拉定理

\[a^b=a^{b mod f(p)+f(p)}(b>f(p)) \]

\[a^b=ksm(a,b,p)(b≤f(p)) \]

递归的,如果我们要处理原问题,转换为

\[ksm(w_l,kmodf(p)+f(p),p),f(p)是欧拉函数 \]

从而转换为求k的过程

 

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsinged long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
#define debugg(x) cout<<#x<<"="<<x<<endl;
#define debug1(x,y) cout<<#x<<' '<<#y<<' '<<x<<' '<<y<<endl;
#define debug cout<<endl<<"*********"<<endl;
#define itn int
#define pii pair<int,int>
#define mid ((all[now].lo+all[now].ro)/2)
#define mod m1
map<ll,ll> eu;
void fre(){
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
}
void fc(){fclose(stdin);fclose(stdout);}
const int maxn=1e5+10;
const ll inf=0x7fffffff;
ll n,m,q,m1;
ll yuan[maxn];
ll ksm(ll a,ll ci,ll mods){
    ll ans=1;
    while(ci>0){
        if(ci%2==1){
            ans=ans*a;
            if(ans>mods) ans=ans%mods+mods;//exeuler
        }
        ci/=2;a=a*a;
        if(a>mods) a=a%mods+mods;
    }
    return ans;
}
ll gcd(ll a,ll b){while(b^=a^=b^=a%=b);return a;}
ll findf(ll now){
    ll eu=now;
    for(int i=2;i<=sqrt(now);i++){
        if(now%i==0) eu=eu-eu/i;
        while(now%i==0) now/=i;
    }
    if(now>1) eu=eu-eu/now;
    return eu;
}
ll dfs(ll lo,ll ro,ll mods){
    if(mods==1) return 1;
    if(lo==ro) return (yuan[lo]>mods)?yuan[lo]%mods+mods:yuan[lo];//exeuler
    return ksm(yuan[lo],dfs(lo+1,ro,eu[mods]),mods);
}
int main(){
    ios;cin>>n>>m;m1=m;
    for(int i=1;i<=n;i++) cin>>yuan[i];
    while(m!=1){eu[m]=findf(m);m=eu[m];}
    eu[1]=1;
    cin>>q;
    for(int i=1;i<=q;i++){
        ll a1,a2;cin>>a1>>a2;
        cout<<dfs(a1,a2,m1)%m1<<endl;//最表层的递归ksm只需要modp不需要%p+p(modp等效但直接输出会多k*p)
    }
    return 0;
}

3811-求逆元的多种方法

问题是求满足

\[1/a=x(modp) \]

的x

1. 扩展欧几里得

\[1=ax(modp)--->ax+yp=1 \]

exgcd(a,b)能够求出形如ax+by=gcd(a,b)的最小整数解

ll x1,y;
void exgcd(ll a,ll b){
    if(!b){x1=1;y=0;return;}
    exgcd(b,a%b);
    ll tmp=x1;x1=y;y=tmp-a/b*y;
}

注意此时得到的x1并不一定是正整数,需使用同余状态下最小整整数转换公式

x1=(x1%b+b)%b;

\[负数取模 -10\%3=-10-\lceil-10/3\rceil\times3=-1 \]

2. 费马小定理

\[a^{p-1}=1(modp),当且仅当p为质数 \]

欧拉定理

\[a^{f(p)}=1(modp),当且仅当gcd(a,p)=1 \]

扩展欧拉定理(欧拉降幂公式)

\[a^b=a^{bmodf(p)}(modp),gcd(a,p)=1 \]

\[a^{bmodf(p)+f(p)}=a^b(modp),gcd(a,p)\neq1,b>f(p) \]

\[a^b=a^b(modp),gcd(a,p)\neq1 ,b<f(p) \]

求解逆元时运用费马小定理

\[a^{p-2}=a^{-1}(modp) \]

故只需ksm(a,p-2,p)即可,exgcd复杂度略优于ksm,适用于单个较大数逆元求解

3. 逆元递推公式
    inv[1]=1;
    for(int i=2;i<=n;i++) inv[i]=(p-p/i)*inv[p%i]%p;
    for(int i=1;i<=n;i++) cout<<inv[i]<<endl;

适用于[1,n](n较小)的逆元求解复杂度O(n)

1516-ax+by=c的最小非负整数解

\[k_{min} =k_{j}mod\frac{l}{gcd(W,l)} \]

求解ax+by=c的最小非负整数解
  1. exgcd(a,b) 需保证a>0,b>0 式子变形后要调整正负(同时转变a,c符号,b待定参数符号改变)

  2. 得到的x1是ax+by=gcd(a,b)的最小非负整数解,应该化简ax+by=c

    \[ans=((c/gcd(a,b)*k1)\%(b/gcd(a,b))+b/gcd(a,b))\%(b/gcd(a,b)) \]

    3807-Lucas定理

    求解形如

    \[C_{m}^{n}=x(modp) \]

    的关于x的方程

    \[C_{m}^{n}=C_{m(mod)p}^{n(mod)p}\times C_{m/p}^{n/p}(modp) \]

    关于Ca1,a2 (a1<p,a2<p)(modp)可以预处理得出

\[C_{a1}^{a2}=a1!/(a2!\times (a1-a2)!) \]

即我们预处理出

  1. 小于模数p的阶乘modp前缀和(此时有一个性质是所有前p-1项阶乘都不被p整除,且前p-1项值是1到p-1的一个排列)
  2. 小于模数p的所有数的逆元inv[i],此时有

    \[ax=1(modp)-->a(xmodp)=ax=1(modp) \]

    所以逆元可以直接由inv[1-(p-1)]的值表示

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define debug cout<<endl<<"*********"<<endl;
#define debugg(x) cout<<#x<<"="<<x<<endl;
#define itn int
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
void fre(){
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
}
void fc(){fclose(stdin);fclose(stdout);}
const int maxn=1e5+10;
const ll inf=0x7fffffff;
int n,m,p,t;
ll zhui[maxn];
int cntp[maxn];
ll inv[maxn];
ll x1,y;
void exgcd(ll a,ll b){
    if(!b){x1=1;y=0;return;}
    exgcd(b,a%b);
    ll tmp=x1;
    x1=y;y=tmp-a/b*y;
}
int ck(int a,int b){
    if(b>a) return 0;//注意边界
    return zhui[a]*(inv[zhui[b]])%p*inv[zhui[a-b]]%p;
}
int lucas(int n,int m){
    if(!m) return 1;//边界
    return lucas(n/p,m/p)*ck(n%p,m%p)%p;
}
int main(){
    ios;fre();
    cin>>t;
    while(t--){
        cin>>n>>m>>p;
        memset(inv,0,sizeof(inv));inv[1]=1;
        for(int i=2;i<p;i++) inv[i]=(p-p/i)*inv[p%i]%p;
        ll ans=1;
        zhui[0]=1;
        for(int i=1;i<=p;i++) zhui[i]=zhui[i-1]*i%p;
        cout<<lucas(n+m,n)<<endl;
    }
    return 0;
}
3868-中国剩余定理

求解方程组

\[x=a_{i}(mod(b_{i})),其中b_{i}两两互质 \]

的最小解

    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) cin>>b[i];
    for(int i=1;i<=n;i++) a[i]=(a[i]%b[i]+b[i])%b[i];
    ll all=1,ans=0;
    for(int i=1;i<=n;i++) all*=b[i];
    for(int i=1;i<=n;i++){
        ll bnow=all/b[i];//bnow与b是互质的
        exgcd(bnow,b[i]);
        x1=(x1%b[i]+b[i])%b[i];
        ans+=ksc(ksc(a[i],x1,all),bnow,all);
        ans%=all;
    }

以及快速乘模版

inline ll ksc(ll a,ll b,ll mod){
    ll ans1=0;
    while(b>0){
        if(b%2) ans1=(ans1+a)%mod;
        a=(a+a)%mod;
        b/=2;
    }
    return ans1;
}
4777-excrt

在中国剩余定理的形式基础上,如果bi不互质,需采用excrt

不会证明看题解区阮行止的证明

    cin>>n;
    for(int i=1;i<=n;i++) m[i]=read(),c[i]=read();
    for(int i=2;i<=n;i++){
        ll cha=((c[i]-c[i-1])%m[i]+m[i])%m[i];
        ll gcd1=exgcd(m[i-1],m[i]);
        if(cha%gcd1){cout<<"No solution.";break;}
        x1=(x1%m[i]+m[i])%m[i];
        c[i]=c[i-1]+x1*cha/gcd1%m[i]*m[i-1];
        m[i]*=m[i-1]/gcd1;
        c[i]=(c[i]%m[i]+m[i])%m[i];
    }
    print(c[n]);
__int128输入输出板子(注意__int128不能开cin优化)
inline __int128 read(){
    __int128 x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
inline void print(__int128 x){
   if(x<0){putchar('-');x=-x;}
   if(x>9) print(x/10);
   putchar(x%10+'0');
}

2480-古代猪文

数论全家桶

实际上是求

\[G^{\sum [d|n]C^{d}_{n}}modp,p=999911659 \]

求上面的累加和modf(p)由于f(p)太大Lucas没法预处理,所以可以分解质因数f(p),2,3,4679,35617,分别使用Lucas求出取模后的值,再联立同余方程组使用crt求解最小整数解,然后使用ksm求得最终解。

另,需要特判Gmodp==0的情况,因为这里使用了扩展欧拉定理解决,

G==p时费马小定理/欧拉定理/扩展欧拉定理不成立

4549-裴蜀定理

ax+by=k*gcd(a,b)

注意使用gcd/exgcd/时需要保证x,y>0
欧拉函数
void getprime(){
    memset(num,1,sizeof(num));
    memset(eu,1,sizeof(eu));
    num[0]=num[1]=0;eu[1]=1;
    for(int i=2;i<=1e7;i++){
        if(num[i]){prime[++cntpri]=i;eu[i]=i-1;}
        for(int j=1;j<=cntpri&&prime[j]*i<=1e7;j++){
            num[prime[j]*i]=0;
            if(gcd(prime[j],i)==1) eu[prime[j]*i]=eu[prime[j]]*eu[i];
            else eu[prime[j]*i]=prime[j]*eu[i];
            if (i%prime[j]==0) break;
        }
    }
}

线性筛

for(int i=2;i<=sqrt(n);i++){
  if(n%i==0) eu=eu*(1-1.0/i);
  while(n%i==0) n/=i;
} 
if(n!=1) eu=eu*(1-1.0/n);

O(√n)求出单个欧拉函数值

2568-求gcd为质数的小于n的有序数对,有

\[zhui[n/prime[j]]*2-1,注意到\varphi(1)=1,且数对为(1,1),此时n/prime[j]=1的数有(3,3),(7,7)等 \]

2158-求正方形点阵中最多能够连成的线的数目

可以发现gcd(x,y)≠1的点不能计入考虑,且具有对称性,

遍历每一个x坐标[2,n-1],对应的加上他的欧拉函数值,最后将前缀和*2+3(考虑垂直和水平线,以及y=x)可以得到答案

2398-

\[∑^n_{i=1} ∑_{j=1}^n gcd(i,j) \]

考虑到gcd为1的数就是∑f(i),i<=n的和*2,gcd为2的数就是∑f(i),i<=n/2的前缀和*2,,gcd最大为n,余数从1到n遍历,每一层循环内f(i)*yu<=n的加上即可。

posted @ 2021-03-25 18:00  14long  阅读(45)  评论(0)    收藏  举报