ACM数论板子


未完 待补。

推荐博客:

[总结]数论和组合计数类数学相关(定理&证明&板子)
[总结]其他杂项数学相关(定理&证明&板子)
总结]多项式类数学相关(定理&证明&板子)
[探究] OI中各种初级数论算法相关

板子:

慢速乘

在模数较大的情况下,防止爆 l o n g   l o n g long\ long long long

ll qmul(ll a,ll b,ll p)
{
    ll ans=0;
    while(b)
    {
        if(b&1)
            ans=(ans+a)%p;
        a<<=1;
        a%=p;
        b>>=1;
    }
    return ans;
}

快速乘

目的同上,但是时间复杂度是 O ( 1 ) O(1) O(1)的。(精度出锅自己负责~ )

ll fmul(ll a,ll b,ll p)
{
    return (a*b-(ll)((long double)a*b/p)*p+p)%p;
}

快速幂

ll qpow(ll a,ll b)
{
    ll t1=a,t2=1;
    while(b)
    {
        if(b&1)
            t2=t2*t1%p;
        t1=t1*t1%p;
        b>>=1;
    }
    return t2;
}

矩阵快速幂

原理和快速幂一样,只不过需要自己构造矩阵、实现矩阵乘法,给一个样例吧。

struct Matrix
{
	ll t[3][3];
	Matrix()
	{
		t[0][0]=t[0][1]=t[2][0]=t[2][2]=1;
		t[1][0]=2;
		t[0][2]=t[1][1]=t[1][2]=t[2][1]=0;
	}
};

Matrix mul(Matrix a,Matrix b)
{
	Matrix c;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			c.t[i][j]=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			for(int k=0;k<3;k++)
				c.t[i][j]=(c.t[i][j]+a.t[i][k]*b.t[k][j])%m;
	return c;
}

Matrix qpow(ll b)
{
	Matrix t1,t2;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
		{
			t1.t[i][j]=(i==j?1:0);
		}
	while(b>0)
	{
		if(b&1)
			t1=mul(t1,t2);
		t2=mul(t2,t2);
		b>>=1;
	}
	return t1;
}

G C D GCD GCD

int gcd(int a, int b) 
{
     return b == 0 ? a : gcd(b, a%b);
} 

E X G C D EXGCD EXGCD

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

另外一种写法:

int exgcd(int a, int b, int &x, int &y) 
{
    if (b == 0) 
    {
        x = 1; y = 0;
        return a;
    }
    int c = exgcd(b, a%b, x, y);
    int t = x;
    x = y;
    y = t-a/b*y;
    return c;
}

t i p : a ∗ x + b ∗ y = g c d ( a , b ) tip:a*x+b*y=gcd(a,b) tipax+by=gcd(a,b),当 a 、 p a、p ap互质时,想要求 a a a p p p的乘法逆元,只需要调用 e x g c d ( a , p , x , y ) exgcd(a,p,x,y) exgcd(a,p,x,y),求得的 x x x即为答案。( x x x可能是个负数,需要注意一下)

费马大定理

在这里插入图片描述

费马小定理

在这里插入图片描述那么我们可以得到逆元的又一求法,因为 a ∗ x = 1 ( m o d    p ) a*x=1(mod\ \ p) ax=1(mod  p),由费马小定理可得 x = a p − 2   m o d   p x=a^{p-2}\ mod\ p x=ap2 mod p

乘法逆元

普通的乘法逆元的求法如上,这里只提一下逆元的线性算法。
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn=3e6+5;

int n,p;
int inv[maxn]; //inv[i] 表示 i 模 p 的乘法逆元

void work()//求出 1-n 所有数在模 p 意义下的乘法逆元 要保证p是一个素数且 n<p
{
    inv[1]=1;
    for(int i=2;i<=n;i++)
        inv[i]=(ll)(p-p/i)*inv[p%i]%p;
}

int main()
{
    scanf("%d %d",&n,&p);
    work();
    for(int i=1;i<=n;i++)
        printf("%d\n",inv[i]);
    return 0;
}


再说一下阶乘逆元的线性求法:
在这里插入图片描述

C R T CRT CRT

t i p s : tips: tips: 代码中求逆元的方式比较通用,如果满足情况的话可以用费马小定理代替。

ll qmul(ll a,ll b,ll p)
{
    ll ans=0;
    while(b)
    {
        if(b&1)
            ans=(ans+a)%p;
        a<<=1;
        a%=p;
        b>>=1;
    }
    return ans;
}

ll fmul(ll a,ll b,ll p)
{
    return (a*b-(ll)((long double)a*b/p)*p+p)%p;
}

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

ll inv(ll a,ll p)// 求 a%p 的乘法逆元
{
    ll x,y;
    exgcd(a,p,x,y);
    return (x%p+p)%p;
}

ll CRT()
{
    ll ans=0,M=1;
    for(int i=1;i<=n;i++)
        M*=m[i];
    for(int i=1;i<=n;i++)
    {
        ll tmp=M/m[i];
        ans=(ans+fmul(fmul(a[i],tmp,M),inv(tmp,m[i]),M))%M;
    }
    return (ans+M)%M;
}

E X C R T EXCRT EXCRT

在这里插入图片描述

ll EXCRT()
{
    ll ans=a[1],M=m[1];
    ll x,y,gcd;
    for(int i=2;i<=n;i++)
    {
        gcd=exgcd(M,m[i],x,y); // M * x + m[i] * y = gcd(M,m[i])
        ll t1=((a[i]-ans)%m[i]+m[i])%m[i];
        ll t2=m[i]/gcd;
        if(t1%gcd!=0)
            return -1; //无解
        x=fmul(x,t1/gcd,t2);
        ans+=x*M;
        M*=t2;
        ans=(ans%M+M)%M;
    }
    return ans;

}

勾股数组

在这里插入图片描述

威尔逊定理

在这里插入图片描述

筛素数

E r a t o s t h e n e s Eratosthenes Eratosthenes筛法:

int MAX=sqrt(1000000+0.5);
	for(int i=2;i<=MAX;i++)
		if(!vis[i])
			for(int j=i*i;j<=1000000;j+=i)
				vis[j]=1;
vis[1]=1;

最大值自己修改即可。

欧拉筛法(线性筛法):

int prime[MAX+5];
bool vis[MAX+5];

void Prime()
{
    int k=-1;
    for(int i=2;i<=MAX;i++)
    {
        if(!vis[i])//是素数
            prime[++k]=i;
        for(int j=0;j<=k&&i*prime[j]<=MAX;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
                break;
        }
    }
}

欧拉公式/定理

在这里插入图片描述上式中的 φ ( m ) φ(m) φ(m)就是下面将要介绍的欧拉函数。

欧拉函数

φ ( x ) φ(x) φ(x)的含义是: [ 1 , x ] [1,x] [1,x]内与 x x x互质的数的个数 ( φ ( 1 ) = 1 ) (φ(1)=1) (φ(1)=1)
在这里插入图片描述
所以我们可以直接求出 φ ( n ) φ(n) φ(n)的值:

ll phi(ll n)       //直接法求欧拉函数值
{
	ll ret=n;
	ll i;
	for(i=2;i*i<=n;i++)
    {
		if(n%i==0)
        {
            ret-=ret/i;
			while(n%i==0)
                n/=i;
        }
	}
	if(n>1)
        ret-=ret/n;
	return ret;
}

欧拉函数还有几个重要的性质:
在这里插入图片描述在这里插入图片描述那么在欧拉筛中加入一些语句,就可以同时在线性时间内得到 [ 1 , n ] [1,n] [1,n]的欧拉函数值:

int prime[10000005];
bool vis[10000005];
int phi[10000005];

int k=-1;

void euler(int n)
{
	phi[1]=1;//1要特判
	for (int i=2;i<=n;i++)
	{
		if(!vis[i])//i是质数
		{
			prime[++k]=i;
			phi[i]=i-1;
		}
		for (int j=0;j<=k&&prime[j]*i<=n;j++)
		{
			vis[i*prime[j]]=1;
			if (i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			else
				phi[i*prime[j]]=phi[i]*phi[prime[j]];//利用了欧拉函数是个积性函数的性质
		}
	}
}

在这里插入图片描述
证明如下:
在这里插入图片描述

欧拉降幂

给定 a 、 s 、 p a、s、p asp,计算 a s    m o d    p a^s\ \ mod\ \ p as  mod  p的值,如果 s s s l o n g   l o n g long\ long long long范围内,那么很简单,快速幂就可以解决。但是如果 s s s非常大,只能用字符串的形式表示,该怎么求解呢?
方法一:基于快速幂的思想,我们依然可以对这个较大的幂进行拆分,举个例子 a 2333 = ( a 1 ) 3 ∗ ( a 10 ) 3 ∗ ( a 100 ) 3 ∗ ( a 1000 ) 2 a^{2333}=(a^1)^3*(a^{10})^3*(a^{100})^3*(a^{1000})^2 a2333=(a1)3(a10)3(a100)3(a1000)2,那么直接上代码:

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;

const int maxn=1e6+5;

ll a,p;
char s[maxn];

inline ll qpow(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1)
            ans=ans*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ans;
}

inline ll cal() //计算 a^s mod p
{
    int len=strlen(s);
    ll ans=1;
    for(int i=len-1;i>=0;i--)
    {
        ans=ans*qpow(a,s[i]-'0')%p;
        a=qpow(a,10)%p;
    }
    return ans;
}

int main()
{
    while(~scanf("%lld%s%lld",&a,s,&p))
        printf("%lld\n",cal());
    return 0;
}

方法二:你这个方法一不行啊,在某些题目下还是会超时啊。那么就需要一种更加优秀的做法——欧拉降幂。
先回顾一下欧拉定理:
在这里插入图片描述下面给出欧拉降幂公式:
在这里插入图片描述这个是广义欧拉降幂公式,就是说不管 A 、 C A、C AC是否互质,都可以用这个公式来计算。当然你仍然需要判断 B B B φ ( C ) φ(C) φ(C)的值,当 B < φ ( C ) B<φ(C) B<φ(C)时可直接用快速幂计算,不能带入上述公式。

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;

const int maxn=2e7+5;

ll a,p;
char s[maxn];

ll euler(ll n)	//得到φ(n)
{
    ll ret=n,i;
    for(i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            n/=i;
            ret=ret-ret/i;
            while(n%i==0)
                n/=i;
        }
    }
    if(n>1)
        ret=ret-ret/n;
    return ret;
}


ll qpow(ll a,ll b)//计算a^b%p
{
    ll ans=1;
    while(b>0)
    {
        if(b&1)
            ans=ans*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ans;
}

int main()
{
    scanf("%lld%lld%s",&a,&p,s);//计算a^s % p
    ll oula=euler(p);
    ll sum=0;
    int len=strlen(s);
    bool flag=0;
    for(int i=0;i<len;i++)
    {
        sum=sum*10+s[i]-'0';
        if(sum>=oula)
            sum%=oula,flag=1;
    }
    if(flag)
        sum+=oula;
    printf("%lld\n",qpow(a,sum));
    return 0;
}

哥德巴赫猜想

在这里插入图片描述

计算模 p p p k k k次根

在这里插入图片描述比较简单,实现就不给了。

M i l l e r − R a b i n Miller-Rabin MillerRabin素数检测

在这里插入图片描述在这里插入图片描述更加详细的内容请自行百度了解。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int prime[12]={2,3,5,7,11,13,17,19,23,29,31,37};

ll fmul(ll a,ll b,ll p)
{
    return (a*b-(ll)((long double)a*b/p)*p+p)%p;
}

ll qpow(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)
            ans=fmul(ans,a,p);
        a=fmul(a,a,p);
        b>>=1;
    }
    return ans;
}

bool Miller_Rabin(ll x)
{
    int s=0;
    ll t=x-1;
    if(x==2)
        return 1;
    if(x<2||!(x&1))
        return 0;
    while(!(t&1))
    {
        s++;
        t>>=1;
    }
    for(int i=0;i<12&&prime[i]<x;++i)
    {
        ll a=prime[i];
        ll b=qpow(a,t,x);
        for(int j=1;j<=s;++j)
        {
            ll k=fmul(b,b,x);
            if(k==1&&b!=1&&b!=x-1)
            	return 0;
            b=k;
        }
        if(b!=1)
            return 0;
    }
    return 1;
}

int main()
{
    ll x;
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld",&x);
        printf("%s\n",Miller_Rabin(x)?"Yes":"No");
    }
    return 0;
}

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值