1. 程式人生 > 其它 >【筆記】基礎數論

【筆記】基礎數論

來自\(\texttt{SharpnessV}\)省選複習計劃中的基礎數論


CF1355F Guess Divisors Count

互動題,給定\(X\le10^{9}\),每次可以詢問\(\gcd (X,Q_i)\),\(Q_i\le 10^{18}\),並在 $22 $ 次詢問內求出 \(X\) 的約數個數,允許有一倍的誤差。

首先 \(\ge 10^3\) 的質數最多\(2\)個,如果存在\(\ge 10^3\)的質數,則約數個數最少\(\times 2\),最多\(\times 4\)。不考慮\(\ge 10^3\)的約數能夠滿足一倍的誤差。

考慮\(\le 10^3\)以內的約數,總數不多,先詢問出有哪些質因子,然後詢問質因子的次冪。

雖然這個方法可以在很少次數內完成,但是特殊構造的資料能然會超過\(22\)次。

首先如果我們找到了一個質因子的次冪,上界\(10^3\)會對應下移。比如找到一個 \(2\),則\(800\)以上的質數可以忽略,因為\(800^3\ge5\times 10^8\)

如果一直沒有找到質數,我們忽略\(\ge 100\) 的質數,因為\(100^5 \ge 10^9\),所以最多也就 \(16\) 個約數,最少 \(2\) 個約數。

Code


P1072 [NOIP2009 提高組] Hankson 的趣味題

\(\gcd (a_0,x)=a_1\ \ \land\ \ \rm lcm(b_0,x)=b_1\)\(x\)

的個數。

$ x $ 一定是 \(b_1\) 的約數,我們列舉 \(b_1\) 的約數並\(\rm check\)即可,因為一個數的約數個數不會很多,所以複雜度是對的。

時間複雜度\(\rm O(N\sqrt{b}+N\sigma(b)\log b)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll gcd(ll a,ll b){
	if(!b) return a;
    else return gcd(b,a%b);
}
inline ll lcm(ll a,ll b){
	return a*b/(gcd(a,b));
}
int main()
{
	int n;ll ans;scanf("%d",&n);ll a0,a1,b0,b1;
	for(int i=1;i<=n;i++){
		ans=0;
		scanf("%lld%lld%lld%lld",&a0,&a1,&b0,&b1);
    	for(int j=1;j*j<=b1;j++)
		  if(b1%j==0){
			if(gcd((ll)j,a0)==a1&&lcm((ll)j,b0)==b1)ans++;
			if(b1/j!=j)
			  if(gcd(b1/j,a0)==a1&&lcm(b1/j,b0)==b1)ans++;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

P2152 [SDOI2009]SuperGCD

高精度取模非常不可做,考慮輾轉相減法。

\(\forall a>b\ \ ,\ \ \rm gcd(a,b)=gcd(b,a-b)\)

但輾轉相減會被卡到\(\rm O(N)\),我們需要二進位制優化。

顯然 \(\forall\ 2|a,b\ \ ,\ \ \rm gcd(a,b)=2gcd(\dfrac{a}{2},\dfrac{b}{2})\)

並且 \(\forall\ 2|a,2\nmid b\ \ ,\ \ \rm gcd(a,b)=gcd(\dfrac{a}{2},b)\)

這樣就能保證複雜度為\(\rm O(\log N)\)


P2158 [SDOI2008]儀仗隊

\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[\gcd (i,j)=1]\)

這裡 \(n\) 很小,直接用\(\varphi\)的定義即可,\(ans=\sum\limits_{i=1}^{n}\varphi(i)\)

\(\varphi\),可以線性篩,也可以質因數分解。

#include<bits/stdc++.h>
using namespace std;
int oula(int x){
	int ans=x;
	for(int i=2;i*i<=x;i++){
		if(x%i==0){
		  ans=ans/i*(i-1);
		  while(x%i==0)x/=i;
	    }
	}
	if(x>1)ans=ans/x*(x-1);
	return ans;
}
int main()
{
	int n,ans=0;scanf("%d",&n);n--;
	for(int i=2;i<=n;i++){
	    ans+=oula(i)*2;
	}
	if(n)ans+=3;
	printf("%d",ans);
	return 0;
}

P2398 GCD SUM

\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\gcd (i,j)\)

顯然約數有 \(d\)\((i,j)\) 對數為 \(\left\lfloor\dfrac{n}{d}\right\rfloor^2\),容斥求出最大公約數為 \(d\) 的對數。

時間複雜度為\(\sum\limits_{i=1}^{n}\frac{n}{i}=\rm O(N\log N)\)

當然也可以反演 \(\varphi *1=\rm Id\)

#include<cstdio>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
long long n,ans,f[100010];
int main(){
    scanf("%lld",&n);
    pre(i,n,1){
        f[i]=n/i*(n/i);
        for(int j=i*2;j<=n;j+=i)f[i]-=f[j];
        ans+=f[i]*i;
    }
    printf("%lld",ans);
	return 0;
}

P2568 GCD

\(\gcd(x,y)=p\)的對數,我們可以求\(\gcd(x_0,y_0)=1\)的對數,然後列舉 \(p\)\(x=x_0\times p,y=y_0\times p\)

互質數對恰好為\(\sum \varphi\),線性篩一下即可。


P4139 上帝與集合的正確用法

費馬小定理

\[\forall a\in P\ ,\ a^p\equiv a\pmod{p} \]

尤拉定理

\[\forall a\perp p\ ,\ a^{\varphi(p)}\equiv 1\pmod{p} \]

擴充套件尤拉定理

\[a^c\equiv\begin{cases}a^c&c\leq\varphi(p)\\a^{c\bmod \varphi(p)+\varphi(p)}&c>\varphi(p)\end{cases} \]

這道題令\(k=2^{2^{\cdots}}\),有\(k\equiv 2^k\pmod{p}\)

所以\(k=2^{k\bmod \varphi(p)+\varphi(p)} \bmod p\)

遞迴下去即可,邊界為 \(p=1\)


P4549 【模板】裴蜀定理

\(ax+by=c\)有整數解的充要條件是 \(\gcd (a,b)\mid c\)

所以裴蜀定理就是\(n\)元整數方程有解的衝要條件是所有係數的最大公約數整除\(c\)

#include<bits/stdc++.h>
using namespace std;
int n,a[25],ans;
int gcd(int x,int y){
	return y?gcd(y,x%y):x;
}
int main()
{
	scanf("%d",&n);
	scanf("%d",&ans);
	for(int i=1;i<n;i++)scanf("%d",&a[i]),ans=gcd(ans,a[i]<0?-a[i]:a[i]);
	printf("%d\n",ans);
	return 0;
}

P2613 【模板】有理數取餘

一般用費馬小定理求逆元,模數不為質數用擴充套件歐幾里得演算法。

#include<bits/stdc++.h>
#define P 19260817
using namespace std;
inline int read()
{
	int sum=0;char ch=getchar();
	while(ch>'9'||ch<'0')ch=getchar();
	while(ch<='9'&&ch>='0')sum=(sum*10+ch-'0')%P,ch=getchar();
	return sum;
}
long long Pow(long long x,int y){
	long long mul=1;
	for(;y;y>>=1,x=(x*x)%P)
		if(y&1)mul=(mul*x)%P;
	return mul;
}
int main()
{
	int a=read();
	int b=read();
	if(!b)puts("Angry!");
	else{
		//cout<<a<<" "<<b<<endl;
		printf("%lld\n",(long long)Pow(b,P-2)*a%P);
	}
	return 0;
	
}

P3811 【模板】乘法逆元

線性求逆元。

\(ki+r=p\)

\[ki+r\equiv0 \]\[kr^{-1}+i^{-1}\equiv0 \]

線性遞推即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define N 3000005
int inv[N],n,p;
int main(){
	scanf("%d%d",&n,&p);
	inv[1]=1;
	rep(i,2,n)inv[i]=-1LL*(1LL*inv[p%i]*(p/i))%p;
	rep(i,1,n)printf("%d\n",inv[i]<0?p+inv[i]:inv[i]);
	return 0;
}

P5431 【模板】乘法逆元2

離線,將所有要求逆元的數乘起來求逆元,再乘上字首積與字尾積即可。

#include<bits/stdc++.h>
using namespace std;
int n,p,k;
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read(){
    int x=0;char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x;
}
void print(int x) {
    if(x>9) print(x/10);
    *O++=x%10+'0';
}
int a[5000005],sum=1,mul[5000005],mul_[5000005];
int Pow(long long x,int y){
	long long now=1;
	for(;y;y>>=1,x=x*x%p)
	  if(y&1)now=now*x%p;
	return now;
}
int main()
{
	n=read();p=read();k=read();
	for(register int i=1;i<=n;i++)
	  a[i]=read(),sum=(long long)sum*a[i]%p,mul[i]=sum;
	mul_[n]=a[n];
	for(register int i=n-1;i;i--)
	  mul_[i]=(long long)mul_[i+1]*a[i]%p;
	sum=Pow(sum,p-2);
	mul[0]=mul_[n+1]=1;int m=k,ans=0;
	for(register int i=1;i<=n;i++)
	  ans=(ans+(long long)sum*mul[i-1]%p*m%p*mul_[i+1]%p)%p,m=(long long)m*k%p;
	print(ans);
	fwrite(obuf,O-obuf,1,stdout);
	return 0;
}

P3951 [NOIP2017 提高組] 小凱的疑惑

答案為\(\rm ab-a-b\),證明可以反證法,直接推導難度較高,可以直接打表。

#include<cstdio>
long long int a, b;
int main()
{
    scanf("%lld%lld",&a,&b);
    printf("%lld",a*b-a-b);
    return 0;
}

P1082 [NOIP2012 提高組] 同餘方程

擴充套件歐幾里得演算法,用於求解形如 \(ax+by=\gcd(a,b)\) 的不定方程。

\[ax\equiv 1\pmod{b} \]

等價於

\[ax+by=1 \]

所以有解的充要條件是\(a\perp b\) 。然後套用擴歐即可。

#include<bits/stdc++.h>
void exgcd(int a,int b,int &x,int &y){
	if(b==0)x=1,y=0;
	else{
		int xx,yy;
		exgcd(b,a%b,xx,yy);
		x=yy;y=xx-a/b*yy;
	}
}
int a,b;
int main(){
	scanf("%d%d",&a,&b);
	int x,y;exgcd(a,b,x,y);
	printf("%d\n",(x%b+b)%b);
	return 0;
}

P1516 青蛙的約會

寫出方程:\((m-n)d\equiv x-y \pmod{L}\)

線性同餘方程,直接\(\rm exgcd\)即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int exgcd(int a,int b,int &x,int &y){
	if(!b){
		x=1;y=0;return a;
	}
	int x0,y0,c;
	c=exgcd(b,a%b,x0,y0);
	x=y0;y=x0-(a/b)*y0;
	return c;
}
signed main()
{
	int x,y,m,n,l;
	scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
	int p,t,c,a,b;
	b=(n-m);a=(x-y);
	if(b<0)b=-b,a=-a;
	c=exgcd(b,l,t,p);
	if(a%c)printf("Impossible\n");
	else printf("%lld\n",((t*(a/c))%(l/c)+(l/c))%(l/c));
	return 0;
}

P3846 [TJOI2007] 可愛的質數/【模板】BSGS

\(\rm BSGS\)演算法,根號分治的一種應用。

我們令\(t=\left\lfloor\sqrt{P}\right\rfloor+1\),預處理\(a^{t},a^{2t},\cdots,a^{t^2}\)\(a^{0},a^1,\cdots,a^{t-1}\)次冪,開一個雜湊表維護即可。

\(b\times a^x\equiv a^y\),則\(a^{y-x}\equiv x\)。時間複雜度\(\rm O(\sqrt N)\),如果用\(\rm map\)是帶\(\log\)的。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
#define int long long
using namespace std;
int p,b,n;
map<int,int>h;
void BSGS(){
	h.clear();
	int a=1,t=sqrt(p*2)+1;
	rep(i,0,t-1){
		h[a*n%p]=i;
		a=a*b%p;
	}
	int k=a;
	rep(i,1,t){
		if(h.count(a)){
			printf("%lld\n",t*i-h[a]);
			return;
		}
		a=a*k%p;
	}
	printf("no solution\n");
}
signed main(){
	scanf("%lld%lld%lld",&b,&p,&n);
	while(p&&b&&n){
		BSGS();
		scanf("%lld%lld%lld",&b,&p,&n);
	}
	return 0;
}

P4195 【模板】擴充套件BSGS

模數不為質數,遞迴處理,需要用\(\rm exgcd\)求逆元。

如果遇到模數不是質數的題噴出題人就完事。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define int long long
using namespace std;
int gcd(int x,int y){return y?gcd(y,x%y):x;}
void exgcd(int a,int b,int &x,int &y){
	if(!b){x=1;y=0;return;}
	int xx,yy;exgcd(b,a%b,xx,yy);
	x=yy;y=xx-(a/b)*yy;
}
int inv(int a,int p){
	int xx,yy;
	exgcd(a,p,xx,yy);
	return (xx%p+p)%p;
}
map<int,int>h;
int BSGS(int a,int b,int p){
	h.clear();a%=p;b%=p;
	int t=sqrt(p)+1,k=1;
	rep(i,0,t-1){
		h[k*b%p]=i;
		k=k*a%p;
	}
	a=k;k=1;
	rep(i,0,t){
		if(h.count(k)){
			int j=h[k];
			if(i*t-j>=0)return i*t-j;
		}
		k=k*a%p;
	}
	return -1;
}
void exBSGS(int a,int b,int p,int mul,int sum){
	if(b==1||p==1){printf("%lld\n",sum);return;}
	int g=gcd(a,p);
	if(b%g){puts("No Solution");return ;}
	if(g==1){
		int ans=BSGS(a,b*inv(mul,p)%p,p);
		if(~ans)printf("%lld\n",ans+sum);
		else puts("No Solution");
		return;
	}
	exBSGS(a,b/g,p/g,(a/g*mul)%p,sum+1);
}
signed main(){
	int a,b,p;scanf("%lld%lld%lld",&a,&p,&b);
	while(a&&b&&p){
		exBSGS(a,b,p,1,0);
		scanf("%lld%lld%lld",&a,&p,&b);
	}
	return 0;
}                      

P5491 【模板】二次剩餘

沒啥用的模板,看看就行。


P2485 [SDOI2011]計算器

三合一的題目。

第一問直接快速冪。

第二問是線性同餘方程,直接\(\rm exgcd\)即可。

第三問直接\(\rm BSGS\)即可。

正好複習一下板子。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 100005
#define int long long
using namespace std;
int Pow(int x,int y,int p){
	int now=1;
	for(;y;y>>=1,x=x*x%p)if(y&1)now=now*x%p;
	return now;
}
int gcd(int x,int y){return y?gcd(y,x%y):x;}
void exgcd(int a,int b,int &x,int &y){
	if(!b){x=1;y=0;return ;}
	int xx,yy;exgcd(b,a%b,xx,yy);
	x=yy;y=xx-(a/b)*yy;
}
map<int,int>h;
void solve(int a,int b,int p){
	h.clear();
	int t=sqrt(p)+1,k=1;
	rep(i,0,t-1){
		h[k*b%p]=i;
		k=(k*a)%p;
	}
	a=k;k=1;
	if(a%p==0&&b){puts("Orz, I cannot find x!");return;}
	rep(i,0,t){
		if(h.count(k)){
			int j=h[k];
			if(i*t-j>=0){
				printf("%lld\n",i*t-j);
				return;
			}
		}
		k=(k*a)%p;
	}
	puts("Orz, I cannot find x!");
}
signed main(){
	int T,op;
	scanf("%lld%lld",&T,&op);
	if(op==1){
		int x,y,z;
		while(T--){
			scanf("%lld%lld%lld",&x,&y,&z);
			printf("%lld\n",Pow(x,y,z));
		}
	}
	else if(op==2){
		int y,z,p,x,X;
		while(T--){
			scanf("%lld%lld%lld",&y,&z,&p);
			int g=gcd(y,p);
			if(z%g){puts("Orz, I cannot find x!");continue;}
			exgcd(y,p,x,X);
			x=((z/g*x)%p+p)%p;printf("%lld\n",x);
		}
	}
	else{
		int a,b,p;
		while(T--)scanf("%lld%lld%lld",&a,&b,&p),solve(a,b,p);
	}
	return 0;
}

P3306 [SDOI2013] 隨機數生成器

基礎數列題。

給定遞推式\(x_i=ax_{i-1}+b\),這就是一階遞推數列,一波推導得到它的通項公式。

\[x_n=a^{n-1}(x_1+\dfrac{b}{a-1})-\dfrac{b}{a-1} \]

解方程

\[x_n=t \]

顯然只有\(n\)未知,直接\(\rm BSGS\)即可。

眾所周知等比數列\(a=1\)需要特判,\(a=0\)也要特判。

Code


P4884 多少個1?

同樣是數列,我們設數列第 \(n\) 項是 \(n\)\(1\)。有遞推式

\[a_n=10a_{n-1}+1 \]

把這個數列的遞推公式解出來就是

\[a_n=\dfrac{10^n-1}{9} \]

現在解\(a_n\equiv K \pmod m\)。直接\(\rm BSGS\)即可。

Code


P4861 按鈕

擴充套件\(\rm BSGS\),出題人應該爬。

Code


P4454 [CQOI2018]破解D-H協議

很新的一道題,看來\(\rm BSGS\)還沒有成為時代的眼淚。

給定\(g^a\)\(g^b\),求\(g^{ab}\)

直接\(\rm BSGS\)解出\(a\),然後快速冪即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define int long long 
using namespace std;
int g,P;
int Pow(int x,int y){
	int now=1;
	for(;y;y>>=1,x=x*x%P)if(y&1)now=now*x%P;
	return now;
}
map<int,int>h;
int BSGS(int x){
	h.clear();
	int a=1,t=sqrt(P)+1;
	rep(i,0,t-1)h[a*x%P]=i,a=a*g%P;
	int k=a;
	rep(i,1,t)if(h.count(k))return i*t-h[k];else k=k*a%P;
	return -1;
}
signed main(){
	scanf("%lld%lld",&g,&P);
	int n;scanf("%lld",&n);
	while(n--){
		int a,b;scanf("%lld%lld",&a,&b);
		int x=BSGS(a);
		printf("%lld\n",Pow(b,x));
	}
	return 0;
}