文章目录
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
题意:给定整数N求1<=x,y<=N且Gcd(x,y)为素数的数对(x,y)有多少对,N<=1e7.
思
路
:
我
们
可
以
依
次
枚
举
素
数
,
对
于
一
个
素
数
P
,
如
果
g
c
d
(
x
∗
P
,
y
∗
P
)
=
P
;
思路: 我们可以依次枚举素数,对于一个素数P,如果gcd(x*P,y*P)=P;
思路:我们可以依次枚举素数,对于一个素数P,如果gcd(x∗P,y∗P)=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可以满足的值,显然是欧拉函数的前缀和。
y∗P<=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=1满足,在欧拉函数里与n与互质且小于n的数.比如:现在P=3,N=9;那么有gcd(1,2)和gcd(1,3),gcd(2,3),可以满足,这正是ϕ(2)+ϕ(3)=3然后一种特殊情况,gcd(1,1),最大公约数是P的对数即是前三项ϕ∗2+1,之所以乘2,是这里题意,gcd(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
d∣N所有的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)=p1n−1ϕ(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
输入只包括一行5个整数x,y,m,n,L,其中x=y<2000000000,0<m、n<2000000000,0<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
(x−y)+(m−n)t=−kL,得:
(
m
−
n
)
t
+
L
k
=
(
y
−
x
)
(m-n)t+Lk=(y-x)
(m−n)t+Lk=(y−x)
通过扩展欧几里得算法得到:方程
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=dcy0−kda.(k∈Z)
求得最小正整数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)
x≡bimod(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)
Mi∗ti≡1 mod(ai)
M
i
t
i
+
m
i
∗
k
=
1
M_it_i+m_i*k=1
Miti+mi∗k=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=1nbi∗Mi∗ti
最终要对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×n矩阵A和一个正整数k,找出总数S=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=A∗S(n−1)+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;
}