【筆記】基礎數論
來自\(\texttt{SharpnessV}\)的省選複習計劃中的基礎數論。
互動題,給定\(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\) 個約數。
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; }
高精度取模非常不可做,考慮輾轉相減法。
\(\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)\)。
求\(\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;
}
求\(\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;
}
求\(\gcd(x,y)=p\)的對數,我們可以求\(\gcd(x_0,y_0)=1\)的對數,然後列舉 \(p\) 令\(x=x_0\times p,y=y_0\times p\)。
互質數對恰好為\(\sum \varphi\),線性篩一下即可。
費馬小定理
\[\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\) 。
\(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;
}
一般用費馬小定理求逆元,模數不為質數用擴充套件歐幾里得演算法。
#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;
}
線性求逆元。
令 \(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;
}
離線,將所有要求逆元的數乘起來求逆元,再乘上字首積與字尾積即可。
#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;
}
答案為\(\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;
}
擴充套件歐幾里得演算法,用於求解形如 \(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;
}
寫出方程:\((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;
}
模數不為質數,遞迴處理,需要用\(\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;
}
沒啥用的模板,看看就行。
三合一的題目。
第一問直接快速冪。
第二問是線性同餘方程,直接\(\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;
}
基礎數列題。
給定遞推式\(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\)也要特判。
同樣是數列,我們設數列第 \(n\) 項是 \(n\) 個 \(1\)。有遞推式
\[a_n=10a_{n-1}+1 \]把這個數列的遞推公式解出來就是
\[a_n=\dfrac{10^n-1}{9} \]現在解\(a_n\equiv K \pmod m\)。直接\(\rm BSGS\)即可。
擴充套件\(\rm BSGS\),出題人應該爬。
很新的一道題,看來\(\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;
}