GCD問題和LCM問題
兩個數的gcd
g=gcd(a,b) 則g<=a && g<=b
ll gcd(ll a,ll b)
{
return b==0?a:gcd(b,a%b);
}
由該算法我們可以知道,給定a,b由a,b進行加減得出來的數都是gcd(a,b)的倍數,並且能得到任意倍數
兩個數的lcm
l=gcd(a,b) 則l>=a && l>=b
ll lcm(ll a, ll b)
{
ll g=gcd(a,b);
return a/g*b;
}
gcd循環(輾轉相除法的原理)
如果gcd(n,m)=g,則g=gcd(n,m)=gcd(n,m+i*n)=gcd(n+i*m,m)=gcd(1,g);其逆運算很關鍵,即我們可以用加法性質來簡化運算較大結果的部分。
質因數大於都m的數
UVa11440
一個數的所有質因數都大於m,這種條件一定要轉化到互質gcd=1纔好用結論處理,因此,我們認爲該數x和小於m的所有質數p互質,也即是,由於我們爲了方便尋找這樣的數,我們能想到互質定理(歐拉定理),由於小於等於m的數都是由以上質數組成,故gcd(x,m!)=1也是成立的。也即是我們可以利用歐拉定理求得小於等於m!的x的個數。再利用gcd循環求得n!中x的個數即可。
擴展歐幾里德
ax+by=gcd(x,y),已知a和b的情況下,求取gcd(a,b),x,y的值
ll exgcd(ll a,ll b,ll &x,ll &y)
{
ll d=a;
if(b==0)x=1,y=0;
else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
return d;
}
歐拉函數
定義: 其中p1,p2...pn都是質數,,歐拉函數表示不超過m,且和m互質的數的個數。
//打出n的表 o(nlog(n))
int eular[maxn];
void get_eular() //phi
{
for(int i=1;i<maxn;i++)
eular[i]=i;
for(int i=2;i<maxn;i++)
if(eular[i]==i)
for(int j=i;j<maxn;j=j+i)
eular[j]=eular[j]/i*(i-1);
}
//歐拉篩打表o(n)
int tot=0;
int phi[maxn],prime[maxn];
bool isPrime[maxn];
void getphi()
{
for(int i=1;i<maxn;i++)isPrime[i]=true;
phi[1]=1;
for(int i=2;i<maxn;i++)
{
if(isPrime[i]==true)prime[++tot]=i,phi[i]=i-1;
for(int j=1;j<=tot;j++)
{
if(i*prime[j]>=maxn)break;
isPrime[i*prime[j]]=false;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}else phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
//直接獲得某個值的歐拉函數值 o(sqrt(n))
int eular(int temp)
{
int result=temp;
int sqrtt=sqrt(temp);
for(int i=2;i<=sqrtt;i++)
{
if(temp%i==0)
{
result=result/i*(i-1);
while(temp%i==0) //剔除相同因數
temp=temp/i;
}
}
if(temp!=1) //剩下一個質數
result=result/temp*(temp-1);
return result;
}
結論:
結論1:m是質數,則φ(m)=m-1,由文自定義容易獲得。
結論2:對於和m互質的數x,x^φ(m) mod m =1;費馬小定理的一般形式 x^(p-1) mod p=1 ( x<p,p爲質數)
故:當 b很大時 ,可以將b縮小範圍
結論3:歐拉函數是積性函數,則對於任意互質的整數a和b有性質f(ab)=f(a)f(b)
結論4:當n爲奇數時, 。從定義容易看出,因爲n中沒有2的因子。
原根
x是奇素數p的原根,當且僅當{x^i %p | 1<=i<=p-1} 等於集合{ 1,...,p-1}。
結論:由於p是質數,他有Phi(p-1)個原根
求取a關於範圍b的互質個數
UVA - 10214
由於歐拉函數只能求取a關於範圍1-a中和a互質的結果,因此,爲了求取範圍1-b中的結果,我們如下考慮gcd的循環:
將b分解成1-a a+1-2*a 2a+1-3a... na+1-b的部分,其中完整的部分有b/a個,最後一部分有a-b/a*a個結果,單獨運算即可
//a在範圍1-b上的互質的個數
int eular(int a,int b)
{
int ans=b/a*eular[a];
for(int i=1;i<=b%a;i++)
if(gcd(a,i)==1)
ans++;
return ans;
}
Farey級數
F(n)(n>=2)表示有一系列不能約分的分數a/b(0<a<b<=n,且gcd(a,b)=1) 組成,求Fn。
由於gcd(a,b)=1,因此我們可以知道就是對於每個b,其中多少個a與之互質,也即是b的歐拉函數。
故Farey[i]=Farey[i-1]+eular[i]; Farey[2]=eular[2]=1;
圖像形式,也即是:看下半部分的,從原點射出一條線段,碰到的第一個點。加上(1,1)(0,1)(1,0)三個點,因爲gcd(x,y)=1
利用莫比烏斯反演可更快得出結果:大致如下
質數問題
定義:除了1和本身以外不含有其他因數的數。也即是對於x來說i取(1,x-1),gcd(i,x)=1。
質數判定
二次探測:
如果x是一個質數,0<m<x,則方程m^2%x=1,只有m=1或m=(x-1)的解
Millar_rabin+二次探測 做法:
記x=(2^j)*m+1,則a^(x-1)%x ?=1,即變成了 檢測a^(2^j*m)%x是否等於1,也即是(a^2)^j * a^m %x ?= 1
//0(sqrt(n))算法
bool isprime(ll x)
{
ll sqrtx=sqrt(x);
for(int i=2;i<=sqrtx;i++)
if(x%i==0)
return false;
return true;
}
// Miller-Rabin測試
ll multi(ll a,ll b,ll n) //大數乘法 a*b mod n 目標乘法變加法,極端消耗時間
{
a=a%n;b=b%n;
ll res=0;
while(b)
{
if(b&1)
{
res=res+a;
if(res>=n)res=res-n;
}
a=a<<1;
if(a>=n)a=a-n;
b=b>>1;
}
return res;
}
ll qpow(ll a,ll b,ll mod) //超大數快速冪
{
ll temp=1;
while(b)
{
if(b&1)
temp=multi(temp,a,mod);
a=multi(a,a,mod);
b=b>>1;
}
return temp;
}
inline ll random(ll n)
{
return (ll)(rand()%(n-1)+1);
}
bool witness(ll a,ll n,ll x,int t)
{
ll r=qpow(a,x,n);
ll last=r;
for(int i=1;i<=t;i++)
{
r=multi(r,r,n);
if(r==1 && last!=1 && last!=n-1)return true;
last=r;
}
if(r!=1)return true;
return false;
}
bool Miller_rabin(ll n)
{
if(n==2 || n==3 || n==5) return true;
if((n<2) || (n&1==0) || (n%3==0) || (n%5==0))
return false;
ll x=n-1,t=0;
while((x&1)==0)x=x>>1,t++;
for(int k=0;k<10;k++)
if(witness(random(n),n,x,t)) //如果rand的值過大
return false;
return true;
}
質數篩
// o(nloglog(n))
bool isPrime[maxn];
int prime[maxn];
int primeNum;
void getPrime()
{
primeNum=0;
for(int i=1;i<maxn;i++)
isPrime[i]=true;
for(int i=2;i<maxn;i++)
{
if(isPrime[i]==true)
{
prime[++primeNum]=i;
for(ll j=(ll)i*(ll)i;j<maxn;j=j+i)
isPrime[j]=false;
}
}
}
// 歐拉篩 O(n) 基於歐拉篩可以簡化很多篩法
int prime[maxn],tot=0;
bool isPrime[maxn];
void getPrime()
{
memset(isPrime,true,sizeof(isPrime));
for(int i=2;i<maxn;i++)
{
if(isPrime[i])prime[++tot]=i;
for(int j=1;j<=tot;j++)
{
if(i*prime[j]>=maxn)break;
isPrime[i*prime[j]]=false;
if(i%prime[j]==0) break;
}
}
}
質數篩-函數法
質數分解
正常分解:
int sqrtx=sqrt(x);
for(int i=2;i<=sqrtx;i++)
{
while(x%i==0)
x=x/i;
}
if(x!=1)
{
/**/
}
先質數篩再分解,節約時間.按照選出來的數進行分解,對於一個數x,只需要篩選出sqrt(x)就好:
bool isPrime[maxn];
int prime[maxn];
int primeNum;
void getPrime()
{
primeNum=0;
for(int i=1;i<maxn;i++)isPrime[i]=true;
for(int i=2;i<maxn;i++)
{
if(isPrime[i]==true)
{
prime[++primeNum]=i;
for(ll j=(ll)i*(ll)i;j<maxn;j=j+i)
isPrime[j]=false;
}
}
}
int main()
{
int sqrtx=sqrt(x);
for(int i=1;prime[i]<=sqrtx;i++)
{
int p=prime[i];
while(x%p==0)x=x/p;
}
if(x!=1)
{
/**/
}
}
Pollard_Rho分解
對於較大一點的數x來說,我們需要求他的sqrt(x)來進行因式分解也是一件比較麻煩的事情,因此我們考慮基於類似Miller_Rabbin的概率算法來處理相應問題。
處理思路是,先用Miller_Rabbin算法判定x是否是一個質數,如果是則進行Pollard分解
//如果太慢 記得把qpow 換成直接快速冪
ll multi(ll a,ll b,ll n) //大數乘法 a*b mod n 目標乘法變加法,極端消耗時間
{
a=a%n;b=b%n;
ll res=0;
while(b)
{
if(b&1)
{
res=res+a;
if(res>=n)res=res-n;
}
a=a<<1;
if(a>=n)a=a-n;
b=b>>1;
}
return res;
}
ll qpow(ll a,ll b,ll mod) //超大數快速冪
{
ll temp=1;
while(b)
{
if(b&1)
temp=multi(temp,a,mod);
a=multi(a,a,mod);
b=b>>1;
}
return temp;
}
inline ll random(ll n)
{
return (ll)(rand()%(n-1)+1);
}
bool witness(ll a,ll n,ll x,int t)
{
ll r=qpow(a,x,n);
ll last=r;
for(int i=1;i<=t;i++)
{
r=multi(r,r,n);
if(r==1 && last!=1 && last!=n-1)return true;
last=r;
}
if(r!=1)return true;
return false;
}
bool Miller_rabin(ll n)
{
if(n==2 || n==3 || n==5) return true;
if((n<2) || (n&1==0) || (n%3==0) || (n%5==0))
return false;
ll x=n-1,t=0;
while((x&1)==0)x=x>>1,t++;
for(int k=0;k<10;k++)
if(witness(random(n),n,x,t)) //如果rand的值過大
return false;
return true;
}
/*
ll gcd(ll a,ll b) //分解出只有正數的快速方法
{
if(a==0)return 1;
if(a<0)return gcd(-a,b);
while(b)
{
ll t=a%b;a=b;b=t;
}
return a;
}
*/
ll gcd(ll a, ll b)
{
return b==0?a:gcd(b,a%b);
}
ll Pollard_rho(ll n,ll c)
{
ll x=random(n);
ll y=x,i=1,k=2;
int num=0;
while(true)
{
i++;
x=(multi(x,x,n)+c)%n;
ll d=gcd(y-x+n,n); //由於用的是遞歸gcd,沒判斷正負,需要傳入正值+n會影響較多速度,gcd中處理較好
if(d!=1 && d!=n)return d;
if(y==x)return n;
if(i==k)
{
y=x;
k=k<<1;
}
}
}
void find_factor(ll n)
{ //遞歸進行質因數分解N
if(Miller_rabin(n)) //尋找到質因數
{
minnum=min(minnum,n); //尋找到一個質因數n,對其進行合適作操作
return;
}
ll p=n;
while (p>=n) p=Pollard_rho(p,random(n));
find_factor(p);
find_factor(n/p);
}
常見結論
1.在10^20的範圍類,兩個質數之間的差值不會超過1000;
MOD問題
mod求和
UVa1363
給定n,k,求
我們將k mod i 進行分組,按照k/i=d的結果進行分組,我們即可看到規律。
當d=0時,k mod i=k.
當d=1時,i取(k/2+1,k)
當d=2時,i取(k/3+1,k/2)
當爲d時,i取(k/(d+1)+1,k/d)
當d>=sqrt(k)時,i取(k/(d+1)+1,k/d),即該範圍不多於1個值,可能無法取值。
故分三段取值d=0 d取(1,sqrt(k)) d>sqrt(k) 第一段直接得結論,第二段 用等差數列得結果,第三段枚舉
逆元問題
我們知道乘法和觸發本質是一樣的,因此在mod k 的前提下,我們需要求取 (a * b / c )mod k 的值,其實就是在求取((a mod k ) *(b mod k ) *( 1/c mod k ) )mod k 而 (1/c mod k)其實就是c在mod k下的逆元叫做inv(c)
//擴展歐幾里德求取a在m下的逆元
ll exgcd(ll a,ll b,ll &x,ll &y)
{
ll d=a;
if(b==0)x=1,y=0;
else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
return d;
}
ll inv(ll a,ll m)
{
ll x,y;
exgcd(a,m,x,y);
return (x%m+m)%m;
}
如果k爲質數,我們可以使用費馬定理,利用快速冪處理
ll qpow(ll a,ll n,ll mod)
{
ll temp=1;
while(n)
{
if(n&1)temp=(temp*a)%mod;
a=(a*a)%mod;
n=n>>1;
}
return temp;
}
ll inv(ll a,ll m)
{
return qpow(a,m-2,m);
}
逆元打表
ll inv[maxn];
void initInv()
{
inv[0]=inv[1]=1;
for(int i=2;i<mod+10;i++)inv[i]=(((-mod/i)*inv[mod%i])%mod+mod)%mod;
}
積性函數
定義:
積性函數:對於任意互質的整數a和b有性質f(ab)=f(a)f(b)的數論函數。
完全積性函數:對於任意整數a和b有性質f(ab)=f(a)f(b)的數論函數。
φ(n) -歐拉函數
μ(n) -莫比烏斯反演,關於非平方數的質因子數目
gcd(n,k) -最大公因子,當k固定的情況
d(n) -n的正因子數目
σ(n) -n的所有正因子之和
莫比烏斯反演
//單獨求解
int mu(int x)
{
int cnt=1;
int sq=sqrt(x);
for(int i=2;i<=x&&i<=sq;i++)
{
if(x%i!=0)continue;
x=x/i;
if(x%i==0)return 0;
cnt=-cnt;
}
if(x!=1)cnt=-cnt;
return cnt;
}
//O(n)
int mu[maxn];
bool vis[maxn];
int prime[maxn],tot;
void getMu()
{
memset(vis,0,sizeof(vis));
mu[1]=1;
tot=0;
for(int i=2;i<maxn;i++)
{
if(vis[i]==false)
{
prime[tot++]=i;
mu[i]=-1;
}
for(int j=0;j<tot;j++)
{
if(i*prime[j]>=maxn)break;
int tmp=i*prime[j];
vis[tmp]=true;
if(i%prime[j]==0)
{
mu[tmp]=0;
break;
}
else mu[tmp]=-mu[i];
}
}
}
多項式問題
差分
給定多項式, 當n可以取1.....n等值,使得計算出來的結果滿足一定的條件,由於在多項式中的係數an可能比較大,使得結果不方便計算,或者由於n的取值比較大使得我們的for循環模擬比較大,我們不妨考慮差分的思想,想一想f(n)-f(n-1)滿足什麼樣的結論。
結論
結論1: 可從方程觀查看未知數個數判斷出結論.
例:uva 1069
給出形如 (P(n))/D 的式子,求出這個式子關於正n取值整數是否全是整數.
由於n的取值很大,我們可以考慮從差分入手,每個值都能被D整除,則他們的差分也能被D整除,則差分的差分也能被D整除,故我們能夠得出一個結論:當n取0-bn時,對於每一個f(n)/D結果都是整數,我們就可以得到一個結論對於n去任意正整數f(n)/D爲整數。
組合數問題
組合數公式: c(p,q)=p! / ((p-q)! * q!)
組合數除法
計算C(p, q) / C(s, r) uva 10375
利用組合數公式進行運算 算則儘量少的運算次數,即q>p/2時,p=p-q
運算時乘大數除大數 乘小數除小數 防止精度越界。
int p,q,r,s;
int main()
{
while(scanf("%d %d %d %d",&p,&q,&r,&s)!=EOF)
{
if(q>p/2)q=p-q;
if(s>r/2)s=r-s;
double ans=1.00;
if(q>s)
{
for(int i=1;i<=s;i++)
ans=ans*(p+1-i)/(r+1-i);
for(int i=s+1;i<=q;i++)
ans=ans*(p+1-i)/i;
}
else
{
for(int i=1;i<=q;i++)
ans=ans*(p+1-i)/(r+1-i);
for(int i=q+1;i<=s;i++)
ans=ans/(r+1-i)*i;
}
printf("%.5f\n",ans);
}
return 0;
}
組合數取模
組合數取模也即是C(m,n)%p,其中p爲質數,怎麼求,有公式可知:.
該定理又叫lucas定理。
故代碼如下:(p較小,且p爲質數)
ll exgcd(ll a,ll b,ll &x,ll &y)
{
ll d=a;
if(b==0)x=1,y=0;
else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
return d;
}
inline ll inv(ll a,ll m)
{
ll x,y;
exgcd(a,m,x,y);
return (x%m+m)%m;
}
ll C(ll n,ll m,ll p)
{
if(m>n)return 0;
ll up=1,down=1;
for(int i=n-m+1;i<=n;i++)up=up*i%p;
for(int i=1;i<=m;i++)down=down*i%p;
return up*inv(down,p)%p;
}
ll Lucas(ll n,ll m,ll p)
{
if(m==0)return 1;
return C(n%p,m%p,p)*Lucas(n/p,m/p,p)%p;
}
當p不爲質數時,我們如何處理呢?
我們知道對p進行分解可以得到p=p1^a1 * p2 ^a2 *.....*pn^an, 且每一項之間兩兩互質;故如果求得X%(p1^a1)=b1,X%(p2^a2)=b2,.......,X%(pn^an)=bn,我們是不是可以用CRT(因爲各項互質)將上式合併得到關於X%p=b的結果b;
假如ai都爲1,即p爲square-free-number,則求解方式直接採用質數的盧卡斯定理即可.
ll exgcd(ll a,ll b,ll &x,ll &y)
{
ll d=a;
if(b==0)x=1,y=0;
else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
return d;
}
inline ll inv(ll a,ll m)
{
ll x,y;
exgcd(a,m,x,y);
return (x%m+m)%m;
}
ll C(ll n,ll m,ll p)
{
if(m>n)return 0;
ll up=1,down=1;
for(int i=n-m+1;i<=n;i++)up=up*i%p;
for(int i=1;i<=m;i++)down=down*i%p;
return up*inv(down,p)%p;
}
ll Lucas(ll n,ll m,ll p)
{
if(m==0)return 1;
return C(n%p,m%p,p)*Lucas(n/p,m/p,p)%p;
}
ll multi(ll a,ll b,ll n)
{
a=(a%n+n)%n;b=(b%n+n)%n;
ll res=0;
while(b)
{
if(b&1)
{
res=res+a;
if(res>=n)res=res-n;
}
a=a<<1;b=b>>1;
if(a>=n)a=a-n;
}
return res;
}
ll crt(ll *a,ll *m,int num)
{
ll lcm=1;
for(int i=0;i<num;i++)lcm=lcm*a[i];
ll ans=0;
for(int i=0;i<num;i++)
ans=ans+multi(multi(inv(lcm/a[i],a[i]),lcm/a[i],lcm),m[i],lcm);
return (ans+lcm)%lcm;
}
int num;
ll a[20],b[20];
ll exLucas(ll n,ll m,ll p)
{
for(int i=0;i<p;i++)
{
scanf("%lld",&a[i]);
b[i]=Lucas(n,m,a[i]);
}
ll ans=crt(a,b,p);
return ans;
}
如果p不是square-free-number,CRT部分只需各數互質,不會出現問題,但是lucas定理部分由於pi^ai不是質數,需要進行改動:
計算C(n,m)%p^t。我們知道,C(n,m)=n!/m!/(n-m)!,若我們可以計算出n!%p^t,我們就能計算出m!%p^t以及(n-m)!%p^t。我們不妨設x=n!%p^t,y=m!%p^t,z=(n-m)!%p^t,那麼答案就是x*reverse(y,p^t)*reverse(z,p^t)(reverse(a,b)計算a對b的乘法逆元)。那麼下面問題就轉化成如何計算n!%p^t。比如p=3,t=2,n=19
n!=1∗2∗3∗4∗5∗6∗7∗8∗9∗10∗11∗12∗13∗14∗15∗16∗17∗18∗19n!=1∗2∗3∗4∗5∗6∗7∗8∗9∗10∗11∗12∗13∗14∗15∗16∗17∗18∗19
=(1∗2∗4∗5∗7∗8∗10∗11∗13∗14∗16∗17∗19)∗36∗(1∗2∗3∗4∗5∗6)=(1∗2∗4∗5∗7∗8∗10∗11∗13∗14∗16∗17∗19)∗36∗(1∗2∗3∗4∗5∗6)
根據這個例子發現,求解n!可以分爲3部分:第一部分是pipi的冪的部分,也就是3636即pi⌊npi⌋pi⌊npi⌋,可以直接求解;第二部分是一個新的階乘,也就是6!即⌊npi⌋!⌊npi⌋!,可以遞歸下去求解;第三部分是除前兩部分之外剩下的數
考慮第三部分如何求解
發現第三部分在模pikipiki意義下是以pikipiki爲週期的,即(1∗2∗4∗5∗7∗8)≡(10∗11∗13∗14∗16∗17)(modpiki)(1∗2∗4∗5∗7∗8)≡(10∗11∗13∗14∗16∗17)(modpiki),所以只求pikipiki長度的即可;但是還剩下一個孤立的19,可以發現剩下孤立的數長度不會超過pikipiki,只需要暴力求解即可
e.最後一個問題是對於求出的m!%pikim!%piki和(n−m)!%piki(n−m)!%piki有可能與pikipiki不互質,無法求逆元
所以要將m!%pikim!%piki和(n−m)!%piki(n−m)!%piki中質因子pipi先全部除去,求出逆元后再全部乘回去
計算n!中質因子p的個數x的公式爲x=⌊np⌋+⌊np2⌋+⌊np3⌋+...x=⌊np⌋+⌊np2⌋+⌊np3⌋+...
遞推式也可以寫爲f(n)=f(⌊np⌋)+⌊np⌋
ll qpow(ll a,ll b,ll m)
{
ll tmp=1;
while(b)
{
if(b&1)tmp=tmp*a%m;
a=a*a%m;
b=b>>1;
}
return tmp;
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
ll d=a;
if(b==0)x=1,y=0;
else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
return d;
}
ll inv(ll a,ll m)
{
ll x,y;
exgcd(a,m,x,y);
return (x%m+m)%m;
}
ll fac(ll n, ll p, ll pk)
{
if(!n)return 1;
ll res=1;
if(n/pk)
{
for(ll i=2;i<=pk;i++)if(i%p)res=res*i%pk;
res=qpow(res,n/pk,pk);
}
for(ll i=2;i<=n%pk;i++)if(i%p)res=res*i%pk;
return res*fac(n/p,p,pk)%pk;
}
ll Lucas(ll n,ll m,ll pi,ll pk)
{
if(m>n)return 0;
ll a=fac(n,pi,pk),b=fac(m,pi,pk),c=fac(n-m,pi,pk),k=0;
for(ll i=n;i!=0;i=i/pi)k=k+i/pi;
for(ll i=m;i!=0;i=i/pi)k=k-i/pi;
for(ll i=n-m;i!=0;i=i/pi)k=k-i/pi;
return a*inv(b,pk)%pk*inv(c,pk)%pk*qpow(pi,k,pk)%pk;
}
ll multi(ll a,ll b,ll n)
{
a=(a%n+n)%n;b=(b%n+n)%n;
ll res=0;
while(b)
{
if(b&1)
{
res=res+a;
if(res>=n)res=res-n;
}
a=a<<1,b=b>>1;
if(a>=n)a=a-n;
}
return res;
}
ll crt(ll *a,ll *m,int num)
{
ll lcm=1;
for(int i=0;i<num;i++)lcm=lcm*a[i];
ll ans=0;
for(int i=0;i<num;i++)
ans=ans+multi(multi(inv(lcm/a[i],a[i]),lcm/a[i],lcm),m[i],lcm);
return (ans+lcm)%lcm;
}
int tot;
ll a[20],b[20],px[maxn];
void divide(ll x)
{
tot=0;
ll sq=(ll)sqrt(x*1.0+2.0);
for(int i=2;i<=sq&&x!=1;i++)
{
if(x%i==0)
{
px[tot]=i,a[tot]=1;
while(x%i==0)a[tot]=a[tot]*i,x=x/i;
tot++;
}
}
if(x!=1)px[tot]=a[tot++]=x;
}
ll exLucas(ll n,ll m,ll p)
{
divide(p);
for(int i=0;i<tot;i++)b[i]=Lucas(n,m,px[i],a[i]);
ll ans=crt(a,b,tot);
return ans;
}
直角三角形
1.畢達哥拉斯定理
所有形如的數,有且僅有一下原型數:
令,其中
暴力搜索得:
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int maxs=sqrt(l);
for(int i=1;i<=maxs;i++)
{
int nn=i*i;
for(int j=i+1;j<=maxs;j++)
{
int mm=j*j;
if()
{
//超過,退出條件
}
if(i%2!=j%2 && gcd(i,j)==1)
{
int x=mm-nn;
int y=2*i*j;
int z=mm+nn;
/*****/
num=num++;
}
}
}
佩爾方程
佩爾方程:即形如 形式的方程;
佩爾方程的解法:即先計算出最小解:,其他所有的解可以用遞推形式寫出來
,寫成冪的形式,也即是
int n,k;
int main()
{
while(cin>>n>>k)
{
int x=0,y; //尋找特解
for(y=1;y<5000;y++)
{
x=sqrt(1+n*y*y);
if(x*x-n*y*y==1)
break;
}
if(y==5000)
{
//no answer
}
//快速冪運算 計算第k個解
Mat e;
e.m[1][1]=x;e.m[1][2]=n*y;
e.m[2][1]=y;e.m[2][2]=x;
e.row=2,e.col=2;
Mat res=mat_pow(e,k-1);
int ans=(res.m[1][1]*x+res.m[1][2]*y)%mod;
printf("%d\n",ans);
}
return 0;
}
中國剩餘定理
給出幾個線性同餘方程:求最小的總的個數
其中m1,m2,mn相互互質;
容易知道,當求得一個結果xtemp的時候,令lcm=m1*m2*..*mn,則結果爲x=xtemp+k*lcm。
其中最小的那個值爲 x=(xtemp%lcm+lcm)%lcm;
引論:對於一個式子 x%a=m,我們很容易寫出 x=(a*k+1)*m,本質上即找到mod a爲1的數,使其乘餘數m即可。
對於多個式子,本質上是這樣多個式子的和,即xtemp=(a1*k1+1)*m1+...+(an*kn+1)*mn
不妨記爲:xtemp=x1*m1+...+xn*mn;
其中xi表示mod ai爲1的數,且爲ki*π(aj)(j!=i),,即xi=ki*(lcm/ai).
故可以得到方程: ki*(lcm/ai)=1(mod ai)
故可以的得到式子:(lcm/ai)*ki+ai*y=1
即可以用exgcd求出ki的值從而得到xi的值,最終得到xtemp的結果。
代碼過程:按照上述結果,先求出lcm,再根據每一個a[i],求出(lcm/ai)*ki+ai*y=1中ki的值,最後求取ki*(lcm/ai)*mi的和
ll exgcd(ll a,ll b,ll &x,ll &y)
{
ll d=a;
if(b==0)x=1,y=0;
else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
return d;
}
inline ll inv(ll a,ll m)
{
ll x,y;
exgcd(a,m,x,y);
return (x%m+m)%m;
}
ll crt(ll *a,ll *m,int num)
{
ll lcm=1;
for(int i=0;i<num;i++)lcm=lcm*a[i];
ll ans=0;
for(int i=0;i<num;i++)ans=(ans+inv(lcm/a[i],a[i])*(lcm/a[i])*m[i])%lcm; //溢出取模
return (ans+lcm)%lcm;
}
擴展中國剩餘定理
擴展中國剩餘定理即a1,a2,a3之間不一定互質的情況出現。
這時候需要對所給出的式子進行兩個兩個的組合,
考慮一下式子:
x ≡ m1 mod a1
x ≡m2 mod a2
所以有x= a1*k1+m1 = a2*k2+m2
即 a1*k1 = a2*k2+m2-m1
由於a1與a2不一定互質,故令d=gcd(a1,a2)。 要求(m2-m1)%d=0;如果不爲0,說明無法得到結果。
上式可以化成(a1/d)*k1=(m2-m1)/d+a2/d*k2;
即 (a1/d)*k1=c(mod (a2/d)); 因爲c不一定是1,故求出a1/d關於a2/d的逆元,也即是 k1=c*inv(a1/d) (mod a2/d)
使用一次exgcd可以將k1temp的結果計算出來。
因爲k1每變化a2/d的值,k2就會變化a1/d的值,則k1=k1temp+(a2/d)*y.
所以x=a1*(k1temp+(a2/d)*y )+m1; 展開可得 x=(a1*k1temp+m1) +(a1*a2/d)*y;
也即是x=(a1*k1temp+m1) (mod (a1*a2/d));
也就是說,找到了這麼一個數,他滿足12等式計算出來的結果,並且每(a1*a2/d)的差都滿足該等式。
合併到最後得到x=m (mod a);也就是x=k*a+m;
代碼過程:
提前定義一個大A和大M,表示x=M(mod A);
對於每一組a1,a2,求出其gcd值d,再代入exgcd(a1/d,a2/d,x,y),求得的x即爲a1/d在a2/d下的逆元,乘以c也即是k1的值。
就可以得到x=(a1*k1temp+m1) (mod (a1*a2/d));
最後x=(m%a+a)%a;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
ll d=a;
if(b==0)x=1,y=0;
else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
return d;
}
ll excrt(ll *a,ll *m,int num)
{
ll A=a[0],M=m[0];
for(int i=1;i<num;i++)
{
ll x,y;
ll d=exgcd(A,a[i],x,y); //求兩個式子a的gcd值,同時求出inv(a1/d)(mod a2/d)的結果
ll c=m[i]-M; //判斷兩個式子組成的結果是否正確
if(c%d)return -1;
c=c/d,A=A/d,a[i]=a[i]/d;
ll k=((x*c)%a[i]+a[i])%a[i];//計算c*inv(a1/d)(mod a2/d),求出k值並防止x過大或者過小
M=M+A*d*k; //獲得新式子的M
A=A*a[i]*d; //獲得新式子的A
M=M%A; //防止M過大過小
}
ll ans=M>0?M:M+A; //當所求數大於0採用。
//ll ans=(M%A+A)%A; //M=A的情況哦,可以爲0次時使用!!!! 不是輸出0
return ans;
}
BSGS
BSGS算法即處理a^x≡b(mod p) (p爲質數)的算法。
已知a,b,p的情況,求對應滿足成立式子的最小x。
由費馬定理可以知道,a^1 %p...a^(p-1)%p會遍歷正好一遍0-p-1 因此x必定在0-p-1之間。
由此創建了bsgs算法,令x=m*i-t 其中m是個定值m=sqrt(p+0.5);即利用平方分割的思想進行處理,在平方分割的每個桶中,遍歷尋找結果。
a^(m*i-t)≡b(mod p) 等價於 ((a^m)^i)≡b*a^t (mod p), 令c=a^m ,得c^i≡b*a^t (mod p)由此我們可以將每一個t對應的[1,m]對應的值存入map中,需要的t值越大越好,因爲用的減法,故每次計算出的值,直接map就好,不用判斷。
ll m=ceil(sqrt(p+0.5)); //獲取n的平方根
hashmap.clear(); //map 放裏面不用每次清空 浪費空間,節約時間
ll a_t=b; //利用連乘求值
for(int t=1;t<=m;t++)
{
a_t=(a_t*a)%p;
hashmap.add(a_t,t); //計算b*a^t(mod p) 反向映射,用結果映射t t爲1的時候計算結果爲b*a mmap[b*a]=t;
}
讓i從[0,m]遍歷,去尋找對應的值是否存在,第一個找到的值對應的i,t就是我們需要的值。
typedef long long ll;
ll qpow(ll a,ll b,ll n) //快速冪,用於求a^m 以及求逆元
{
ll temp=1;
while(b)
{
if(b&1)temp=(temp*a)%n;
a=(a*a)%n;
b=b>>1;
}
return temp;
}
ll bsgs(ll a,ll b,ll p)
{
ll m=ceil(sqrt(p+0.5)); //獲取n的平方根
hashmap.clear(); //map 放裏面不用每次清空 浪費空間,節約時間
ll a_t=b; //利用連乘求值
for(int t=1;t<=m;t++)
{
a_t=(a_t*a)%p;
hashmap.add(a_t,t); //計算b*a^t(mod p) 反向映射
}
ll c=qpow(a,m,p); //獲取c
ll c_i=1;
for(int i=1;i<=m;i++) //計算
{
c_i=(c_i*c)%p;
ll num=hashmap.get(c_i);
if(num!=-1)return m*i-num;
}
return -1;
}
int main()
{
ll p,b,n;
while(scanf("%lld %lld %lld",&p,&b,&n)!=EOF)
{
ll result=bsgs(b,n,p);
if(result==-1)
printf("no solution\n");
else
printf("%lld\n",result);
}
return 0;
}
擴展BSGS
擴展BSGS即在取mod的時候,p不一定是質數。
優先判斷,當b==1的時候,即a^0==1 返回0;
即a^x≡b(mod p) 可以寫成 a^x=kp+b, 由於a和p不互質,故存在d=gcd(a,p);使得a/d * a^(x-1)=k*(p/d)+b/d;成立,因爲三個部分都是整數,故b/d一定是整數。如果不是,說明不存在這樣的式子可以成立。
記tmp=a/d,p=p/d,b=b/d 故上式變成了 a/d * a^(x-1)=k*p+b; 即tmp*a^(x-1)=b(mod p),同理繼續上述操作,直到d=gcd(a,p)=0;
有tmp=a/d1 * a/d2 *......*a/dk p=p/d1/d2../dk b=b/d1/d2.../dk a^(x-k)
故上式變成 tmp*a^(x-k)=b(mod p);
在這個計算過程中如果發現了某一個tmp==b 說明了a^(x-k)==1,故直接返回k即可。
ll tmp=1,k=0; //tmp被分割出來的部分的值 k進行了多少次gcd分割
while(true)
{
ll d=gcd(a,p);
if(d==1)break;
if(b%d) return -1;
k++;
b=b/d;p=p/d;
tmp=(a/d*tmp)%p;
if(tmp==b)return k;
}
處理完後,即可發現上式變成了a^(x-k)=b/tmp(mod p);
這裏需要處理b/tmp(mod p),由於先需要處理b/tmp的最小公倍數,再將化簡後的結果用exgce求取tmp關於p的逆元,因爲p不一定是質數,故不能用費馬定理求逆元。用exgcd來求逆元,
b=(b*inv(tmp,p))%p;
用一次常規bsgs即可
ll result=bsgs(a,b,p);
if(result==-1)return -1;
else return result+k;
總的代碼
struct HashNode
{
ll key;
ll val;
int next;
};
const int hashsize=31643,maxnode=100000;
struct Hash
{
HashNode node[maxnode];
int head[maxnode];
int tot;
void clear()
{
memset(head,-1,sizeof(head));
tot=0;
}
void add(ll key,ll val)
{
ll x=key%hashsize;
node[tot].key=key;
node[tot].val=val;
node[tot].next=head[x];
head[x]=tot++;
}
ll get(ll key)
{
ll x=key%hashsize;
for(int i=head[x];i!=-1;i=node[i].next)
if(node[i].key==key) return node[i].val;
return -1;
}
};
Hash hashmap;
ll qpow(ll a,ll b,ll n) //快速冪,用於求a^m 以及求逆元
{
ll temp=1;
while(b)
{
if(b&1)temp=(temp*a)%n;
a=(a*a)%n;
b=b>>1;
}
return temp;
}
//擴展歐幾里德求取a在m下的逆元
ll exgcd(ll a,ll b,ll &x,ll &y)
{
ll d=a;
if(b==0)
{
x=1;y=0;
}
else
{
d=exgcd(b,a%b,y,x);
y=y-(a/b)*x;
}
return d;
}
ll inv(ll a,ll m)
{
ll x,y;
exgcd(a,m,x,y);
return (x%m+m)%m;
}
ll bsgs(ll a,ll b,ll p)
{
ll m=ceil(sqrt(p+0.5)); //獲取n的平方根
hashmap.clear(); //map 放裏面不用每次清空 浪費空間,節約時間
ll a_t=b; //利用連乘求值
for(int t=1;t<=m;t++)
{
a_t=(a_t*a)%p;
hashmap.add(a_t,t); //計算b*a^t(mod p) 反向映射,用結果映射t t爲1的時候計算結果爲b*a mmap[b*a]=t;
}
ll c=qpow(a,m,p); //獲取c
ll c_i=1;
for(int i=1;i<=m;i++) //計算
{
c_i=(c_i*c)%p;
ll num=hashmap.get(c_i);
if(num!=-1)return m*i-num;
}
return -1;
}
ll gcd(ll a ,ll b)
{
return (b==0?a:gcd(b,a%b));
}
ll exbsgs(ll a,ll b,ll p)
{
if(b==1)return 0; //特殊情況先考慮
ll tmp=1,k=0; //tmp被分割出來的部分的值 k進行了多少次gcd分割
while(true)
{
ll d=gcd(a,p);
if(d==1)break;
if(b%d) return -1;
k++;
b=b/d;p=p/d;
tmp=(a/d*tmp)%p;
if(tmp==b)return k;
}
b=(b*inv(tmp,p))%p;
ll result=bsgs(a,b,p);
if(result==-1)return -1;
else return result+k;
}
// 538 23454 14704
int main()
{
ll k,p,n;
while(scanf("%lld %lld %lld",&k,&p,&n)!=EOF)
{
if(n>p)
{
puts("Orz,I can’t find D!");
continue;
}
ll result=exbsgs(k,n%p,p);
if(result==-1)printf("Orz,I can’t find D!\n");
else printf("%lld\n",result);
}
return 0;
}
伯努利數
給定k,計算i的k次方求和轉換爲多項式求和的係數