《算法竞赛进阶指南》------数论习题篇0


PS:做习题,是为了加深对数论知识的印象和理解并提升自己,
既然是习题,就不过多赘述算法原理了,更多在对题意和代码的解释上,好了,我们现在开始吧~.

0x01:Gcd (* 欧拉筛)

传送门
题 意 : 给 定 整 数 N 求 1 < = x , y < = N 且 G c d ( x , y ) 为 素 数 的 数 对 ( x , y ) 有 多 少 对 , N < = 1 e 7 题意:给定整数N求1<=x,y<=N且Gcd(x,y)为素数的数对(x,y)有多少对,N<=1e7 N1<=x,y<=NGcd(x,y)(x,y),N<=1e7.
思 路 : 我 们 可 以 依 次 枚 举 素 数 , 对 于 一 个 素 数 P , 如 果 g c d ( x ∗ P , y ∗ P ) = P ; 思路: 我们可以依次枚举素数,对于一个素数P,如果gcd(x*P,y*P)=P; P,gcd(xP,yP)=P; 那么 g c d ( x , y ) = 1 gcd(x,y)=1 gcd(x,y)=1
不妨假设 x < y x<y x<y,那么 y ∗ P < = N , 枚 举 所 有 的 y 可 以 满 足 的 值 , 显 然 是 欧 拉 函 数 的 前 缀 和 。 y*P<=N,枚举所有的y可以满足的值,显然是欧拉函数的前缀和。 yP<=N,y
如 果 x = y , 只 有 x = y = 1 满 足 , 在 欧 拉 函 数 里 与 n 与 互 质 且 小 于 n 的 数 . 比 如 : 现 在 P = 3 , N = 9 ; 那 么 有 g c d ( 1 , 2 ) 和 g c d ( 1 , 3 ) , g c d ( 2 , 3 ) , 可 以 满 足 , 这 正 是 ϕ ( 2 ) + ϕ ( 3 ) = 3 然 后 一 种 特 殊 情 况 , g c d ( 1 , 1 ) , 最 大 公 约 数 是 P 的 对 数 即 是 前 三 项 ϕ ∗ 2 + 1 , 之 所 以 乘 2 , 是 这 里 题 意 , g c d ( 2 , 4 ) 和 g c d ( 4 , 2 ) 没 有 看 成 一 种 情 况 , 所 以 乘 以 2. 如果x=y,只有x=y=1满足, 在欧拉函数里与n与互质且小于n的数. 比如:现在P=3,N=9; 那么有gcd(1,2)和gcd(1,3),gcd(2,3),可以满足 ,这正是\phi(2)+\phi(3)=3 然后一种特殊情况,gcd(1,1),最大公约数是P的对数即是前三项\phi*2+1, 之所以乘2,是这里题意,gcd(2,4)和gcd(4,2)没有看成一种情况,所以乘以2. x=y,x=y=1nn.P=3,N=9;gcd(1,2)gcd(1,3),gcd(2,3),ϕ(2)+ϕ(3)=3gcd(1,1),Pϕ2+1,2gcd(2,4)gcd(4,2)2.
总 结 : 欧 拉 筛 , 欧 拉 函 数 前 缀 , g c d 总结:欧拉筛,欧拉函数前缀,gcd ,,gcd
ACcode

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e7+100;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll phi[N],v[N],prime[N];
int n,m;
void oula(){
	int n=N-20;
	for(int i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i;
			prime[++m]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=m;j++){
			if(prime[j]>v[i] or prime[j]*i>n) break;
			v[i*prime[j]]=prime[j];
			phi[i*prime[j]]=phi[i]*(i%prime[j]? prime[j]-1:prime[j]);
		}
	}
	// phi[1]=1;
	for(int i=1;i<=n;i++){
          phi[i]+=phi[i-1];
	}
	// rep(i,2,10){
	// 	 cout<<"i:"<<i<<" prime:"<<prime[i]<<" "<<endl;
		 
	// }
	// rep(i,1,10){
	// 	cout<<"i:"<<i<<" v:"<<v[i]<<" phi:"<<phi[i]<<endl;
	// }
	// printf("%d",prime[m]);
}
int pre(int ans,int ind){
	while(ans*ind>n){
           ind--;
	}
	return ind;
}
void solve(){
	n=read();
	int ind=n;
	ll sum=0;
	for(int i=1;i<=m;i++){
		if(prime[i]>n) break;
		ind=pre(prime[i],ind);
		// printf("i:%d prime:%d ind:%d\n",i,prime[i],ind);
		sum+=1+2*phi[ind];
	}
	printf("%lld\n",sum);
	return ;
}
int main (){
//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);
  oula();
  solve();s
  getchar();
  getchar();
  return 0;
}

0x02:Longge’s problem (* 欧拉函数)

传送门

题意:多组输入N,求每组 ∑ g c d ( i , N ) , i ∈ [ 1 , N ] , N ∈ ( 1 , 2 31 ) \sum gcd(i,N),i\in[1,N], N\in(1,2^{31}) gcd(i,N),i[1,N],N(1,231)
思路:质因数分解咯,对 N = p 1 k 1 p 2 k 2 . . . . p m k m . N=p_1^{k_1}p_2^{k_2}....p_m^{k_m}. N=p1k1p2k2....pmkm.分析,找出 d ∣ N d|N dN所有的d,这些所有的d一定是从质因数分解中取出来的。 于是就会有 这样 ( 1 + p 1 + p 1 2 . . p 1 k 1 ) ( 1 + p 2 + p 2 2 . . p 2 k 2 ) ( 1 + p m + p m 2 . . p m k m ) (1+p_1+p_1^{2}..p_1^{k_1})(1+p_2+p_2^{2}..p_2^{k_2})(1+p_m+p_m^{2}..p_m^{k_m}) (1+p1+p12..p1k1)(1+p2+p22..p2k2)(1+pm+pm2..pmkm)他们的因式分解不合并后,值就是n/d. 根据欧拉函数 p 1 p_1 p1 p 2 p_2 p2互质,于是有 ϕ ( p 1 p 2 ) = ϕ ( p 1 ) ϕ ( p 2 ) , ϕ ( p 1 n ) = p 1 n − 1 ϕ ( p 1 ) . \phi(p_1p_2)=\phi(p_1)\phi(p_2),\phi(p_1^n)=p_1^{n-1}\phi(p_1). ϕ(p1p2)=ϕ(p1)ϕ(p2),ϕ(p1n)=p1n1ϕ(p1). 依据这样于是就有: d = ( n / p 1 k 1 p 2 k 2 ) 等 等 。 有 ϕ ( p 1 k 1 p 2 k 2 ) 个 这 样 的 d d=(n/{p_1^{k_1}p_2^{k_2}})等等。有\phi(p_1^{k_1}p_2^{k_2})个这样的d d=(n/p1k1p2k2)ϕ(p1k1p2k2)d
总结:质因数分解,多项式分解,欧拉函数 然后化简得到

请添加图片描述

代码如下:

#include<iostream>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll n;
void solve(){
	while(scanf("%lld",&n)!=EOF){
		ll sum=1;
		// 不多说啥, i是ll , 用int i溢出 出现TLE
		for(ll i=2;i*i<=n;i++){
			if(n%i==0){
				int k=0;
				ll pk=1;
				while(n%i==0){
					k++;
					n/=i;
                    pk*=i;
				}
				// pk/=i;
				sum*=(pk+k*(i-1)*pk/i);
			}
		}
		if(n>1){
			sum*=(2*n-1);
		}
		printf("%lld\n",sum);
	}
	return ;
}
int main (){
//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

0x03: 青蛙的约会 (扩展欧几里得)

传送门
题意在这里插入图片描述
输 入 只 包 括 一 行 5 个 整 数 x , y , m , n , L , 其 中 x ≠ y < 2000000000 , 0 < m 、 n < 2000000000 , 0 < L < 2100000000 输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000 5xymnLx=y<20000000000<mn<20000000000<L<2100000000
思路:可以得到这样一个等式: ( x + m t ) ≡ ( y + n t ) m o d   L (x+mt)\equiv (y+nt) mod\ L (x+mt)(y+nt)mod L
就可以得到: ( x − y ) + ( m − n ) t = − k L (x-y)+(m-n)t=-kL (xy)+(mn)t=kL,得: ( m − n ) t + L k = ( y − x ) (m-n)t+Lk=(y-x) (mn)t+Lk=(yx)
通过扩展欧几里得算法得到:方程 a x + b y = c ax+by=c ax+by=c的通解可以表示为: x = c d x 0 + k b d , y = c d y 0 − k a d . ( k ∈ Z ) x=\frac{c}{d}x_0+k\frac{b}{d},y=\frac{c}{d}y_0-k\frac{a}{d}.(k\in Z) x=dcx0+kdb,y=dcy0kda.(kZ)
求得最小正整数t。
总结:扩展欧几里得
ACcode:

#include<iostream>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	ll d=exgcd(b,a%b,x,y);
	ll z=x;
    x=y;
	y=z-(a/b)*y;
	return d;
}
ll n,m,x,y;
ll L;
void solve(){
    x=read();
	y=read();
	m=read();
	n=read();
	L=read();
	ll a=m-n,b=L;
	ll c=y-x;
	ll d=exgcd(a,b,x,y);
	if(c%d==0){	 
	// printf("a:%d b:%d x:%d y:%d d:%d\n",a,b,x,y,d);
	ll ps= abs(b/d);
	 ll ans=(c/d*x%ps + ps ) % ps;
	 printf("%lld\n",ans);
	}else {
	  printf("Impossible\n");
	}
}
int main (){
//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

0x04:Xiao 9*大战朱最学 (中国剩余定理)

传送门
题意在这里插入图片描述
第一行包含一个整数 n n n 表示建立牛棚的次数。接下来 n n n 行,每行两个整数 a i a_i ai, b i b_i bi,表
示建立了 a i a_i ai 个牛棚,有 b i b_i bi 头牛没有去处。你可以假定不同 a i a_i ai 之间互质
思路 x ≡ b i m o d ( a i ) x\equiv b_i mod(a_i) xbimod(ai)
M = ∏ i = 1 n a i M=\prod^{n}_{i=1} a_i M=i=1nai, M i = M / a i . M_i=M/a_i. Mi=M/ai.
其中寻找出最小的 t i t_i ti,满足 M i ∗ t i ≡ 1   m o d ( a i ) M_i*t_i\equiv 1\ mod(a_i) Miti1 mod(ai)
M i t i + m i ∗ k = 1 M_it_i+m_i*k=1 Miti+mik=1 扩展欧几里得,得出最小非负整数 t i t_i ti
x = ∑ i = 1 n b i ∗ M i ∗ t i x=\sum_{i=1}^{n}b_i*M_i*t_i x=i=1nbiMiti
最终要对x取最小非负整数,即是x=(x%M+M)%M.
总结:中国剩余定理和扩展欧几里得。
ACcode

#include<iostream>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	ll d=exgcd(b,a%b,x,y);
	ll z=x;
    x=y;
	y=z-(a/b)*y;
	return d;
}
ll a[14],b[14];
void solve(){
	int n=read();
	ll ans=0;
	ll M=1;
	for(int i=1;i<=n;i++){
		scanf("%d %d",&a[i],&b[i]);
		M*=a[i];
	}
	for(int i=1;i<=n;i++){
		ll x,y;
		ll Mi=M/a[i];
		exgcd(Mi,a[i],x,y);
		ll ti=(x%a[i]+a[i])%a[i];
		ans=(ans+b[i]*Mi*ti)%M;
	}
   printf("%lld\n",ans);
}
int main (){
//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

0x05:计算器 BZOJ2242 (BSGS)

传送门

题意在这里插入图片描述
思路:板子题,注意一下细节
总结:快速幂, 扩展欧几里得,BSGS

Accode

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll p;
ll qpow(ll a,ll x){
	ll ans=1;
	while(x){
		if(x&1){
			ans=(ans*a)%p;
		}
		a=(a*a)%p;
		x>>=1;
	}
	return ans;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
      if(b==0){
		  x=1;
		  y=0;
		  return a;
	  }
	  ll d=exgcd(b,a%b,x,y);
	  ll z=x;
	  x=y;
	  y=z-(a/b)*y;
	  return d;
}
void cal2(ll a,ll b,ll p){
	 ll x,y;
     ll d=exgcd(a,p,x,y);
	 if(b%d==0){
		 ll ns=p/d;
         ll ans=(b/d*x%ns+ns)%ns;
		 printf("%lld\n",ans);
	 }else printf("Orz, I cannot find x!\n");
}
map<ll,int> mp;
void cal3(){
	 ll a,b;
	scanf("%lld %lld %lld",&a,&b,&p);
	    a%=p;
		if(a==0 and b==0)
		{
			printf("%d\n",1);
			return ;
		} 
		if(a==0)
		{
			printf("Orz, I cannot find x!\n");
			return ;
		}
    	 //注意一个小细节,就是在快速幂中是p哦,别写成了mod啦,一个小小的坑~
    	  ll m=ceil(sqrt(p));
    	  mp.clear();
    	  ll ans=b%p;
    	  //枚举区间i:1-m 
    	  for(int i=1;i<=m;i++){
				ans=ans*a%p;
				mp[ans]=i;
		  }
		  ans=1;
		  int flag=0;
		  ll  t=qpow(a,m);
		  for(int i=1;i<=m;i++){
			  ans=ans*t%p;
		  	if(mp[ans]){
                printf("%lld\n",(i*m-mp[ans]+p)%p);
		  		flag=1;
		  		break;
			  }
		  }
		  if(!flag) printf("Orz, I cannot find x!\n");
}
void solve(){
	 int T,k;
	 T=read();
	 k=read();
	 ll y,z;
	 while(T--){
	 if(k==1){
		 scanf("%lld %lld %lld",&y,&z,&p);
		 ll sum=qpow(y,z);
		 printf("%lld\n",sum);
	 }else if(k==2){
		 scanf("%lld %lld %lld",&y,&z,&p);
        cal2(y,z,p);
	 }else if(k==3){
         cal3();
	 }
	 }
	 return ;
}
int main (){
//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

0x06:Matrix Power Series (* 矩阵快速幂,不仅仅对一个数,一个小矩阵也可)

传送门

题意 给 出 n × n 矩 阵 A 和 一 个 正 整 数 k , 找 出 总 数 S = A + A 2 + A 3 + … + A k . 给出n × n矩阵A和一个正整数k,找出总数S = A + A^2 + A^3 + … + A^k. n×nAkS=A+A2+A3++Ak. 在这里插入图片描述
思路: 令 S n = A + A 2 + A 3 + … + A k S_n=A + A^2 + A^3 + … + A^k Sn=A+A2+A3++Ak 所以: S n = A ∗ S ( n − 1 ) + A S_n=A*S_(n-1)+A Sn=AS(n1)+A 于是可得到: 请添加图片描述

总结:矩阵快速幂, 矩阵分块。
ACcode

#include<iostream>
#include<cstring>
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll mod;
struct Matrix{
	ll  mat[100][100];
	int n, m;
	Matrix(){
		n = m = 20;
		memset(mat, 0, sizeof(mat));
	}
	//重新定义矩阵的大小 
	void init(int row,int col){
		n = row;
		m = col;
	}
	//单位矩阵 
	void init_e(){
		rep(i,1,n){
			rep(j,1,m){
				mat[i][j] = (i == j);
			}
		}
	}
	//打印矩阵 
	void print(){
		rep(i,1,n){
			rep(j,1,m){
				cout<<mat[i][j]<<" ";
			}
			cout<<endl;
		}
		cout<<endl;
	}
};
// 矩阵加法 
Matrix operator +(Matrix a,Matrix b){
	Matrix ret;
	ret.init(a.n,a.m);
	rep(i,1,a.n){
		rep(j,1,a.m){
			ret.mat[i][j] = (a.mat[i][j] + b.mat[i][j])%mod;
		}
	}
	return ret;
}
// 矩阵乘法 
Matrix  operator *(Matrix a,Matrix b){
	Matrix ret;
	ret.init(a.n, b.m);
	rep(i,1,a.n){
		rep(j,1,b.m){
			rep(k,1,a.m){
				ret.mat[i][j] = (ret.mat[i][j]+a.mat[i][k] * b.mat[k][j])%mod;
			}
		}
	}
	return ret;
}
// 矩阵快速幂  求递归方程 
Matrix operator ^ (Matrix a,ll b){
	// n X n
	Matrix sum = a;
	//sum.init(a.n, a.m);
	sum.init_e();
	//a=a*a;
	//return a;
	while(b){
		if(b&1){
			sum = sum * a;
		}
		a = a * a;
		b = b >> 1;
	}
	return sum;
}
ll k,n;
void solve(){
	n=read();
	k=read();
	mod=read();
	Matrix a,b,c;
    Matrix A;
	a.init(2*n,2*n);
    A.init(n,n);
	rep(i,1,n){
		rep(j,1,n){
			A.mat[i][j]=read();
		}
	}
	rep(i,1,a.n){
		rep(j,1,a.m){
			if(i==j)
             a.mat[i][j]=1;

		}
	}
	rep(i,1,n){
		rep(j,1,n){
			a.mat[i][j+n]=A.mat[i][j];
			a.mat[i][j]=A.mat[i][j];
		}
	}
	// a.print();
	a=a^(k-1);
	b.init(2*n,n);
	rep(i,1,2*n){
		rep(j,1,n){
			if(i<=n){
				b.mat[i][j]=A.mat[i][j];
			}
			if(i>n and i-n==j){
                b.mat[i][j]=1;
			}
		}
	}
	// b.print();
	c=a*b;
	rep(i,1,n){
		rep(j,1,n){
			printf("%d ",c.mat[i][j]);
		} printf("\n");
	}
	return ;
}
int main (){
//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);
//   int T; cin>>T;
//   while(T--){
  solve();
  getchar();
  getchar();
  return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axtices

谢谢您的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值