数论习题总结
A、洛谷 P1072 Hankson 的趣味题
题目描述
Hanks 博士是 BT(Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫 Hankson。现在,刚刚放学回家的 Hankson 正在思考一个有趣的问题。
今天在课堂上,老师讲解了如何求两个正整数c1 和 c2 的最大公约数和最小公倍数。现在 Hankson 认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数a0,a1,b0,b1,设某未知正整数x 满足:
1. x 和 a0 的最大公约数是 a1;
2. x 和 b0 的最小公倍数是b1。
Hankson 的“逆问题”就是求出满足条件的正整数x。但稍加思索之后,他发现这样的x 并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的 x* 的个数。请你帮助他编程求解这个问题。
输入格式
第一行为一个正整数n,表示有n 组输入数据。接下来的n* 行每行一组输入数据,为四个正整数 a0,a1,b0,b1,每两个整数之间用一个空格隔开。输入数据保证 a0 能被 a1 整除,b1 能被b0整除。
输出格式
共 n行。每组输入数据的输出结果占一行,为一个整数。
对于每组数据:若不存在这样的x,请输出 0;
若存在这样的x,请输出满足条件的x 的个数;
输入输出样例
输入 #1
2
41 1 96 288
95 1 37 1776
输出 #1
6
2
说明/提示
【说明】
第一组输入数据,xx可以是 9,18,36,72,144,288,共有6 个。
第二组输入数据,xx 可以是48,1776,共有 2 个。
【数据范围】
对于 50%的数据,保证有 1≤a0,a1,b0,b1≤10000 且n≤100。
对于 100%的数据,保证有 1≤a0,a1,b0,b1≤2,000,000,000 且 n≤2000。
NOIP 2009 提高组 第二题
分析
题目的大意就是求同时满足
(gcd(x,a_0)=a_1)
(lcm(x,b_0)=b_1)
的元素(x)的个数
很显然,要想满足上面的条件,必须有
(gcd(a_0/a_1,x/a_1)=1) 并且 (gcd(b_1/b_0,b_1/x)=1)
同时我们还要顺便把(b_1/i)也判断一下,这样就可以降低时间复杂度,从1枚举到(sqrt{b_1})就可以了
总的时间复杂度为(2000 imes sqrt{2000000000}=1e8)
代码
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
ll gcd(ll aa,ll bb){
if(bb==0) return aa;
return gcd(bb,aa%bb);
}
int main(){
int t;
scanf("%d",&t);
while(t--){
int a0,a1,b1,b0;
scanf("%d%d%d%d",&a0,&a1,&b0,&b1);
ll p=a0/a1,q=b1/b0,ans=0;
for(ll x=1;x*x<=b1;x++){
if(b1%x==0){
if(x%a1==0 && gcd(x/a1,p)==1 && gcd(q,b1/x)==1) ans++;
int y=b1/x;
if(x==y) continue;
if(y%a1==0 && gcd(y/a1,p)==1 && gcd(q,b1/y)==1) ans++;
}
}
printf("%lld
",ans);
}
}
B、HDOJ 2824 The Euler function
题目描述
The Euler function phi is an important kind of function in number theory, (n) represents the amount of the numbers which are smaller than n and coprime to n, and this function has a lot of beautiful characteristics.
Here comes a very easy question: suppose you are given a, b, try to calculate (a)+ (a+1)+....+ (b)
输入格式
There are several test cases. Each line has two integers a, b (2<a<b<3000000).
输出格式
Output the result of (a)+ (a+1)+....+ (b)
样例
样例输入
3 100
样例输出
3042
分析
题意大概是:给两个数,求这两个数之间的数的欧拉函数值。
数据范围不大,直接线性筛求1~n的欧拉函数值,最后再枚举一遍相加就可以了
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3000005;
int phi[maxn],prime[maxn],tot;
bool not_prime[maxn];
void getphi(){
int i,j,k;
phi[1]=1;
for(i=2;i<maxn;i++){
if(!not_prime[i]){
prime[++tot]=i;
phi[i]=i-1;
}
for(j=1;j<=tot;j++){
k=i*prime[j];
if(k>=maxn) break;
not_prime[k]=1;
if(i%prime[j]==0){
phi[k]=prime[j]*phi[i];
break;
} else {
phi[k]=(prime[j]-1)*phi[i];
}
}
}
}
int main(){
getphi();
int aa,bb;
while(scanf("%d%d",&aa,&bb)!=EOF){
long long ans=0;
for(int i=aa;i<=bb;i++){
ans+=(long long)phi[i];
}
printf("%lld
",ans);
}
return 0;
}
C. HDU 2588 GCD
题目描述
The greatest common divisor GCD(a,b) of two positive integers a and b,sometimes written (a,b),is the largest divisor common to a and b,For example,(1,2)=1,(12,18)=6. (a,b) can be easily found by the Euclidean algorithm. Now Carp is considering a little more difficult problem: Given integers N and M, how many integer X satisfies 1<=X<=N and (X,N)>=M.
输入格式
The first line of input is an integer T(T<=100) representing the number of test cases. The following T lines each contains two numbers N and M (2<=N<=1000000000, 1<=M<=N), representing a test case.
输出格式
For each test case,output the answer on a single line.
样例
样例输入
3
1 1
10 2
10000 72
样例输出
1
6
260
分析
题目大意就是给你两个数n和m,让你求1<=X<=n 中 gcd(x,n)>=m的x的个数
我们先假设(gcd(x,n)=a)
那么就有(x=a imes k_1,n=a imes k_2)
代入原式就会得到(gcd(k_1,k_2)=1)
所以原题就可以转化为求出(n)的所有因数(p)
使得(gcd(x/p,n/p)=1)的元素(x)的个数
显然(x/pleq n/p),我们只要求出(n/p)的欧拉函数值就可以了
从1枚举到(n)显然是不现实的,所以我们可以像B题一样枚举到(sqrt{n})
一次同时搞掉两个因数
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e6+5;
typedef long long ll;
ll getphi(ll xx){
ll ans=xx;
ll m=sqrt(xx+0.5);
for(ll i=2;i<=m;i++){
if(xx%i==0){
ans=ans/i*(i-1);
}
while(xx%i==0) xx/=i;
}
if(xx>1) ans=ans/xx*(xx-1);
return ans;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
ll n,m;
scanf("%lld%lld",&n,&m);
ll ans=0;
for(ll i=1;i*i<=n;i++){
if(n%i) continue;
if(i>=m) ans+=getphi(n/i);
if(n/i>=m&&i*i!=n) ans+=getphi(i);
}
printf("%lld
",ans);
}
return 0;
}
D、洛谷 P2158[SCOI 2008] 仪仗队
题目描述
作为体育委员,C君负责这次运动会仪仗队的训练。仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。 现在,C君希望你告诉他队伍整齐时能看到的学生人数。
输入格式
共一个数N
输出格式
共一个数,即C君应看到的学生人数。
输入输出样例
输入 #1
4
输出 #1
9
说明/提示
【数据规模和约定】
对于 100% 的数据,1 ≤ N ≤ 40000
分析
我们可以把矩阵看成一个平面直角坐标系
以体育委员所在的点为原点,分别向其它的点作一条直线
根据数学知识可得,如果点的坐标为((x,y))
则直线的斜率为(y/x)
很显然,如果有多条直线的斜率相同,那么只能观察到一个点
换句话说,只有当(gcd(x,y)=1)时,点((x,y))才会被看到
因为会有一行一列的点在坐标轴上,所以我们要把(n--)
最后我们从2到n把欧拉函数值累加,最后再乘2
因为我们相当于以(y=x)为界限把原图划分为两部分
输出的结果的时候要把结果加3,因为((0,1)(1,0)(1,1))三个点没有考虑
当(n=1)的时候还要特判一下
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=50005;
int phi[maxn],prime[maxn];
bool isnot_prime[maxn]={1,1};
void getphi(int xx){
phi[1]=1;
for(int i=2;i<=xx;i++){
if(!isnot_prime[i]){
prime[++prime[0]]=i;
phi[i]=i-1;
}
for(int j=1;j<=prime[0] && i*prime[j]<=xx;j++){
isnot_prime[i*prime[j]]=1;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
} else {
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
}
int main(){
int n;
scanf("%d",&n);
if(n==1){
printf("0
");
return 0;
}
getphi(n);
int ans=3;
for(int i=2;i<=n-1;i++){
ans+=phi[i]*2;
}
printf("%d
",ans);
return 0;
}
E. 洛谷 P2303 Longge的问题
题目背景
Longge 的数学成绩非常好,并且他非常乐于挑战高难度的数学问题。
题目描述
现在问题来了:给定一个整数 n,你需要求出 $$sum_{i=1}^n gcd(i,n)$$,其中 (gcd(i, n)) 表示 i 和 n的最大公因数。
输入格式
输入只有一行一个整数,表示 n。
输出格式
输出一行一个整数表示答案。
输入输出样例
输入 #1
6
输出 #1
15
说明/提示
数据规模与约定
- 对于 60% 的数据,保证 n≤(2^{16})。
- 对于 100% 的数据,保证 1≤n≤(2^{32})。
分析
如果直接去暴力枚举的话,显然会T掉
所以我们考虑一下(gcd)的某些性质
首先(gcd(i,n))所得到的值必定是(n)的某一个因数
拿我们就可以用(sqrt{n})的效率枚举n的每一个因子,也就是最终(gcd)的结果
如果n%i=0的话,那么(i)对于答案的贡献就是$varphi(n/i) imes i $
为什么呢,我们可以这么想
如果要使(gcd(n,a)=i),必定有(a=k imes i (1 le kle n/i))
并且(gcd(k,n/i)=1)
也就是求出区间([1,n/i])中与(n/i)互质的数的个数,这正是欧拉函数的定义
同时也要把另一个因数搞一下
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll phi(ll xx){
ll ans=xx;
ll m=sqrt(xx+0.5);
for(ll i=2;i<=m;i++){
if(xx%i==0){
ans=ans/i*(i-1);
while(xx%i==0) xx/=i;
}
}
if(xx>1) ans=ans/xx*(xx-1);
return ans;
}
int main(){
ll n;
scanf("%lld",&n);
ll ans=0;
for(ll i=1;i*i<=n;i++){
if(n%i==0){
ans+=i*phi(n/i);
if(i*i!=n)ans+=n/i*phi(i);
}
}
printf("%lld
",ans);
return 0;
}
F. 沙拉公主的困惑
题目描述
大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。现在,请你帮助沙拉公主解决这个问题,由于可能张数非常大,你只需计算出对R取模后的答案即可。R是一个质数。
输入格式
第一行为两个整数T,R。(Rleq 10^{9}+10),(Tleq 10000),表示该组中测试数据数目,R为模后面T行,每行一对整数N,M,见题目描述 m<=n
输出格式
共T行,对于每一对N,M,输出1至N!中与M!素质的数的数量对R取模后的值
样例
样例输入
1 11
4 2
样例输出
1
数据范围:
对于100%的数据,1 < = N , M < = 10000000
分析
此题的大致题意为 求1.....n!中所有与 m! 互质的数的个数
因为(m le n),所以必定有(m! le n!)
很显然(n!)一定是$ m!$的倍数
所以我们将(n!)拆成(n!/m!)块,那么每一块中与(m!)互质的数的数量相同
那么最终的答案就是(frac{n!}{m!} imes varphi(m!))
已知(φ(n)= n* frac{p1-1}{p1}* frac{p2-1}{p2}*...... frac{pi-1}{pi})
其中(p_i)为(m!)的质因数
因为(m!)是从1连乘到m的
根据欧拉函数的求法,我们从小到大枚举质因子
很显然,质因子的大小一定不会超过m
那么求(pi)就变成了求1到m中质数的个数,可以用线性筛预处理
(frac{n!}{m!} imes varphi(m!)=frac{n!}{m!} imes m! imes frac{p1-1}{p1}* frac{p2-1}{p2}*...... frac{pi-1}{pi} =n! imes frac{p1-1}{p1}* frac{p2-1}{p2}*...... frac{pi-1}{pi})
因为结果要取模,而除法运算不能像乘法运算那样直接取模
所以我们还要处理出(p_i)在模R意义下的乘法逆元
所以我们可以打出下面的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e7+5;
int ny[maxn];
ll mod;
int prime[maxn];
ll ans[maxn],jc[maxn];
bool not_prime[maxn];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
void get_prime(){
ny[1]=1;
jc[1]=1;
for(register ll i=2;i<maxn;i++){
jc[i]=(ll)i*jc[i-1]%mod;
ny[i]=(mod-mod/i)*ny[mod%i]%mod;
if(!not_prime[i]){
prime[++prime[0]]=i;
}
for(int j=1;j<=prime[0] && i*prime[j]<maxn;j++){
not_prime[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
ans[1]=1;
for(int i=2;i<maxn;i++){
ans[i]+=ans[i-1];
if(!not_prime[i]) ans[i]=ans[i]*(ll)ny[i]%mod*(i-1)%mod;
}
}
int main(){
int t;
t=read(),mod=read();
get_prime();
while(t--){
int n,m;
n=read(),m=read();
printf("%lld
",ans[m]*jc[n]%mod);
}
return 0;
}
但是这样做是不对的
比如
1 3 5 4
正解 1 错解 0
1 7 11 9
正解 5 错解 0
因为洛谷上的数据比较水,所以这样就可以过
但是我们仔细考虑一下,并非所有的情况下都存在乘法逆元,当且仅当 gcd(a,b)=1即a,b互质时,存在乘法逆元
而我们在运算过程中用到了(p_i)在模R意义下的乘法逆元
首先(p_i)必定为质数
对于任何一个正整数x,如果x不等于(p_i),那么就会有(gcd(x,p_i)=1)
这时就可以求出逆元
但是,要是当(x=p_i)的时候,(gcd(x,p_i) e1),逆元就没有意义
这一道题要求(p_i)在%R意义下的逆元
如果R不等于(p_i),那么上面的做法就没有影响
但是如果恰好存在R等于(p_i),那么逆元就没有意义,做法错误
(n! imes frac{p1-1}{p1}* frac{p2-1}{p2}*...... frac{pi-1}{pi})
所以我们如果遇到这种情况,就要把(n!)和分母位置的(p_1*p_2*......*p_i)同时消去一个R
这样就可以了
但是当n>=R 并且 m<R的时候要特判一下,否则会多除一个R
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e7+5;
int ny[maxn];
ll mod;
int prime[maxn];
int anss[maxn],ansx[maxn];
int jc[maxn];
bool not_prime[maxn];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
void get_prime(){
ny[1]=1;
jc[1]=1;
for(int i=2;i<maxn;i++){
if(i==mod) jc[i]=(ll)jc[i-1];
else jc[i]=(ll)i*(ll)jc[i-1]%mod;
if(i%mod!=0) ny[i]=(ll)(mod-mod/i)*(ll)ny[mod%i]%mod;
if(!not_prime[i]){
prime[++prime[0]]=i;
}
for(int j=1;j<=prime[0] && i*prime[j]<maxn;j++){
not_prime[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
anss[1]=1,ansx[1]=1;
for(int i=2;i<maxn;i++){
anss[i]=anss[i-1];
ansx[i]=ansx[i-1];
if(!not_prime[i]){
anss[i]=(ll)anss[i-1]*(ll)(i-1)%mod;;
if(i%mod==0){
ansx[i]=(ll)ansx[i-1];
} else {
ansx[i]=(ll)ansx[i-1]*(ll)ny[i%mod]%mod;
}
}
}
}
int main(){
int t;
t=read();
scanf("%lld",&mod);
get_prime();
while(t--){
int n,m;
n=read(),m=read();
if(n>=mod&&m<mod) puts("0");
else printf("%lld
",1ll*anss[m]%mod*ansx[m]%mod*jc[n]%mod);
}
return 0;
}
G. 同余方程
题目描述
求关于x的同余方程 (a x equiv 1 pmod {b}) 的最小正整数解。
输入格式
一行,包含两个正整数 a,b,用一个空格隔开。
输出格式
一个正整数$ x_0$,即最小正整数解。输入数据保证一定有解。
输入输出样例
输入 #1
3 10
输出 #1
7
说明/提示
【数据范围】
对于 40%的数据,2 ≤b≤ 1,000;
对于 60%的数据,2 ≤b≤ 50,000,000;
对于 100%的数据,2 ≤a, b≤ 2,000,000,000。
NOIP 2012 提高组 第二天 第一题
分析
就是一道很裸的扩展欧几里得板子题
我们把原式转换一下可以得到(ax-by=1)
我们把负号和(y)绑在一起,就变成了(ax+by=1)
那么怎么和扩展欧几里得(ax+by=gcd(a,b))联系在一起呢,我们引用一下洛谷学委的证明
方程 $ax + by = m $有解的必要条件是 (m mod gcd(a,b) = 0)。
由最大公因数的定义,可知 a 是 (gcd(a,b)) 的倍数,且 (b) 是 gcd(a,b) 的倍数,
若 x,y都是整数,就确定了$ ax + by$ 是 gcd(a,b) 的倍数,
因为 m = ax + by,所以 m必须是 gcd(a,b) 的倍数,
那么 (m mod gcd(a,b) = 0)
所以这道题的(gcd(a,b)=1),那问题就迎刃而解了
代码
#include<bits/stdc++.h>
using namespace std;
int ex_gcd(int a,int b,int &x,int &y){
if(b==0){
x=1,y=0;
return a;
}
int ans=ex_gcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
return ans;
}
int main(){
int a,b;
scanf("%d%d",&a,&b);
int xx,yy;
int mygcd=ex_gcd(a,b,xx,yy);
int now=b/mygcd;
printf("%d
",(xx%now+now)%now);
return 0;
}
H. 青蛙的约会
题目链接
分析
很容易想到,如果他们相遇,他们初始的位置坐标之差(x-y)和跳的距离((n-m)t)(设(t)为跳的次数)之差应该是模纬线长(l)同余的,即((n-m)tequiv (x-y)(mod l))
我们把这一个式子变换一下,可以得到((n-m)t-kl=x-y)
显然又是一个扩展欧几里得
所以我们只要求出((n-m)t-kl=gcd((n-m),l))的结果
最后把得到的结果乘一个(frac{x-y}{gcd((n-m),l)})就可以了
要注意的是,如果(n-m<0)的话,我们要把方程两边同时取反,这样求gcd才有意义
至于无解的情况,参照上一题
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ex_gcd(ll aa,ll bb,ll &x,ll &y){
if(bb==0){
y=0,x=1;
return aa;
}
ll ans=ex_gcd(bb,aa%bb,x,y);
ll t=x;
x=y;
y=t-aa/bb*y;
return ans;
}
int main(){
ll x,y,m,n,l,xx,yy;
scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
ll aa=n-m;
ll cc=x-y;
if(aa<0) aa=-aa,cc=-cc;
ll bb=l;
ll gcd=ex_gcd(aa,bb,xx,yy);
if(cc%gcd){
printf("Impossible
");
return 0;
}
ll adt=bb/gcd;
xx=((xx*(cc/gcd)%adt)+adt)%adt;
printf("%lld
",xx);
return 0;
}
I. 倒酒
题目链接
分析
我们设最后剩余的酒量为(ans)
那么必定有(ans=-a*k_1+b*k_2)
很显然,又要利用扩展欧几里得定理
我们先求出(a*k_1+b*k_2=gcd(a,b))的解
由G题证明可得,若要有解,则$ans mod gcd(a,b)=0 $
要使得ans最小,那么ans只能等于(gcd(a,b))
最后再把解转化为最小非负的就可以了
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
ll ex_gcd(ll aa,ll bb,ll &x,ll &y){
if(bb==0){
x=1;
y=0;
return aa;
}
int ans=ex_gcd(bb,aa%bb,x,y);
ll t=x;
x=y;
y=t-aa/bb*y;
return ans;
}
int main(){
ll aa,bb;
scanf("%lld%lld",&aa,&bb);
ll xx,yy;
ll mygcd=ex_gcd(aa,bb,xx,yy);
xx*=(-1),aa*=(-1);
ll ada=bb/mygcd,adb=aa/mygcd;
while(xx<0 || yy<0){
xx+=ada*(ll)(xx<0);
yy-=adb*(ll)(xx>=0);
}
printf("%lld
%lld %lld
",mygcd,xx,yy);
return 0;
}
J. 乘法逆元
分析
裸的板子,没什么好说的
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e6+5;
typedef long long ll;
ll ny[maxn],n,p;
int main(){
ll n,p;
scanf("%lld%lld",&n,&p);
ny[1]=1;
for(ll i=2;i<=n;i++){
ny[i]=(p-p/i)*ny[p%i]%p;
}
for(ll i=1;i<=n;i++) printf("%lld
",ny[i]);
return 0;
}
K. SHUFFLE 洗牌
题目链接
分析
通过观察可得,每进行一次操作,牌的标号就会乘2
我们假设这一张牌的标号为k
那么就会有(k imes 2^{m} mod (n+1)= l)
也就是(k imes 2^{m} - l = (n+1)*p)
等式两边同除以(l),就会有(k/l imes 2^{m} - 1 = (n+1)*p/l)
很显然,这就是求(2^{m})在模(n+1)意义下的逆元
最后再把结果乘上(l)就可以
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,k,mod;
ll ksm(ll xx,ll zs){
ll ans=1;
while(zs){
if(zs&1ll) ans=ans*xx%mod;
xx=xx*xx%mod;
zs>>=1ll;
}
return ans%mod;
}
ll get_phi(ll xx){
ll m=sqrt(xx+0.5);
ll ans=xx;
for(ll i=2;i<=m;i++){
if(xx%i==0){
ans=ans/i*(i-1);
while(xx%i==0) xx/=i;
}
}
if(xx>1) ans=ans/xx*(xx-1);
return ans;
}
int main(){
scanf("%lld%lld%lld",&n,&m,&k);
ll mans;
mod=n+1;
ll ny=ksm(ksm(2,m),get_phi(mod)-1);
mans=ny*k%mod;
printf("%lld
",mans);
return 0;
}
总结
这些题都比较基础,其中扩展欧几里得使用的频率比较高
只要出现(ax+by=c)的形式,我们都可以把它用扩展欧几里得来求解