大一寒假培训(五)——Gcd&Lcm与快速幂取模

大一寒假培训(五)——Gcd&Lcm与快速幂取模

现在先学习一下最大公约数与最小公倍数的相关知识

求最大公约数的方法:
1.辗转相除法(欧几里得算法)
2.辗转相减法(更相减损术)
3.素因子法(最后提一下)

辗转相除法

欧几里得算法又叫辗转相除法,用来求得两个数的最大公约数,记作gcd(a,b)
其原理如下:
设有n,m(m>n),n和m的最大公约数为t 那么:n=k1t, m=k2t,
则 xn+ym=(xk1+yk2)t,
所以 关于n,m的任意线性组合 xn+ym 也能被t整除。
已知 xn+ym 能被t整除
已知 m>n , 不妨有m=k1·n+r1, 其中 r1为余数
变形得到: r1=m-k1·n,
这里r1为一个m和n的线性组合,所以r1也能被t整除
又因为和n都包含了t这个公因子所以有
n=k2·r1+r2⇒r2=k2·r1-n,r2也能被t整除
所以我们r2和r1都包含t这个因子,我们继续上述的方法直到
r1=k3·r3+0,这里的r3就是我们要求的t了
那么,怎么用程序实现欧几里得算法?
实际上我们可以写两种形式的辗转相除:递归和非递归。

int gcd(int a,int b)
{
	int r=a%b;
	while(r)
	{
		a=b;
		b=r;
		r=a%b;
	}
	return b;
}
int gcd(int a,int b)
{
	return b?gcd(b,a%b):a;
}

辗转相减法

辗转相减法的原理和辗转相除法类似
执行的操作是:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
更相减损术和辗转相除法的主要区别在于前者所使用的运算是“减”,后者是“除”。从算法思想上看,两者并没有本质上的区别,但是在计算过程中,如果遇到一个数很大,另一个数比较小的情况,可能要进行很多次减法才能达到一次除法的效果,从而使得算法的时间复杂度退化为O(N),其中N是原先的两个数中较大的一个。相比之下,辗转相除法的时间复杂度稳定于O(logN)。
除此之外,相减的算法对于0和负数要特判,否则死循环。
代码如下。

int gcd(int x,int y)
{
	while(x!=y)
	{
		if(x>y) x-=y;
		else    y-=x;
	}
	return x;
}
int gcd(int x,int y)
{
	if(x==y) return x;
	return x>y?gcd(x-y,y):gcd(y-x,x);
}

素因子法

对于a和b,求它们的最大公约数,那么先把a和b质因数分解,如下
a=p1a1p2a2…pkak
b=p1b1p2b2…pkbk
那么,a和b的最大公约数就可以这样计算:
gcd(a,b)=p1min(a1,b1)p2min(a2,b2)…pkmin(ak,bk)
则最小公倍数
lcm(a,b)=p1max(a1,b1)p2max(a2,b2)…pkmax(ak,bk)

lcm为最小公倍数
lcm(a,b)=a·b/gcd(a,b)
当然,再求lcm时我们一般写成:
lcm(a,b)=a/gcd(a,b)·b
这样可以防止a·b溢出

gcd的一些性质

gcd(a , b) = gcd(b , a-b)
gcd(ma , mb) = m·gcd(a , b), m为一个自然数
gcd(a+mb , b) = gcd(a , b)
m=gcd(a , b) 则gcd(a/m,b/m)=gcd(a,b)/m
gcd(a, lcm(b, c)) = lcm(gcd(a, b), gcd(a, c))(之后的题会用到)
lcm(a, gcd(b, c)) = gcd(lcm(a, b), lcm(a, c))

最后说一句在c++中有已给出的求最大公约数的函数:__gcd(a,b)
直接用即可

nefu 1077 最大公约数和最小公倍数

Description
请计算2个数的最大公约数和最小公倍数;(最大公约数可以使用辗转相除法,最小公倍数=2个数的乘积/它们的最大公约数;)
Input
输入数据有多组,每组2个正整数a,b(2 Output
在一行内输出a和b的最大公约数和最小公倍数;
Sample Input
15 10
Sample Output
5 30

#include 
using namespace std;
int gcd(int a,int b)
{return b ? gcd(b,a%b) : a;}
int main()
{
    int a,b;
    while(cin>>a>>b){
        int gcdn=gcd(a,b);
        int lcmn=a/gcdn*b;
        printf("%d %d\n",gcdn,lcmn);
    }
    return 0;
}

nefu 992 又见GCD

Description
有三个正整数a,b,c(06),其中c不等于b。若a和c的最大公约数为b,现已知a和b,求满足条件的最小的c。
Input
每行输入两个正整数a,b。
Output
输出对应的c,每组测试数据占一行
Sample Input
6 2
12 4
Sample Output
4
8

#include 
using namespace std;
int gcd(int a,int b)
{return b ? gcd(b,a%b) : a;}
int main()
{
    int a,b,c;
    while(cin>>a>>b){
        for(c=b+1;gcd(a,c)!=b;c++)
            ;	//由题意可得出,c>b,当gcd(a,c)==b时循环停止
        printf("%d\n",c);
    }
    return 0;
}

nefu 764 多个数的最大公约数

Description
给定n(n<=10)个正整数,你的任务就是求它们的最大公约数,所有数据的范围均在long long内。
Input
输入数据有多组,每组2行,第一行为n,表示要输入数字的个数,接下来第二行有n个正整数。
Output
输出一个数,即这n个数的最大公约数。
Sample Input
5
2 4 6 8 10
2
13 26
Sample Output
2
13

#include 
using namespace std;
long long gcd(long long a,long long b)
{return b ? gcd(b,a%b) : a;}
long long num[15],gcdn;
int n;
int main()
{
    while(cin>>n){
        for(int i=0;i<n;i++)
            cin>>num[i];
        gcdn=num[0];
        for(int i=1;i<n;i++)	//若要求多个数的最大公约数,那么可以两两合并
            gcdn=gcd(gcdn,num[i]);
        printf("%lld\n",gcdn);
    }
    return 0;
}

nefu 765 多个数的最小公倍数

Description
给定n(n<=10)个正整数,你的任务就是求它们的最小公倍数,所有数据的范围均在long long内。
Input
输入数据有多组,每组2行,第一行为n,表示要输入数字的个数,接下来第二行有n个正整数。
Output
输出一个数,即这n个数的最小公倍数。
Sample Input
5
2 4 6 8 10
2
13 26
Sample Output
120
26

#include 
using namespace std;
long long gcd(long long a,long long b)
{return b ? gcd(b,a%b) : a;}
long long num[15],lcmn;
int n;
int main()
{
    while(cin>>n){
        for(int i=0;i<n;i++)
            cin>>num[i];
        lcmn=num[0];
        for(int i=1;i<n;i++)	//求多个数的最小公倍数,同前题,两两合并
            lcmn=lcmn/gcd(lcmn,num[i])*num[i];
        printf("%lld\n",lcmn);
    }
    return 0;
}

nefu 1411 LCM&GCD

Description
BD最近沉迷于数论,她最近在研究最小公倍数和最大公约数,她的老师Z给她留了一个作业:在[x,y]区间中,求两个整数最大公约数是x且最小公倍数是y的个数。
Input
第一行输入一个T(T<=300),表示有T组数据,接下来输入两个数 x, y(1<=x<=y<=106)(含义如题)
Output
输出一行表示答案
Sample Input
1
2 12
Sample Output
4
Hint
(2,12) (4,6) (6,4) (12,2)

设a,b为符合题意的两个数
则gcd(a,b)=x,lcm(a,b)=y
则x·y=gcd(a,b)·lcm(a,b)=a·b
那么只要a与x·y/b的最大公约数为x即可

#include 
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b)
{return b ? gcd(b,a%b) : a;}
int main()
{
    ios::sync_with_stdio(false);
    ll t,i;
    ll x,y;
    while(cin>>t){
        while(t--){
            cin>>x>>y;
            int ans=0;
            ll ji=x*y;
            for(i=x;i<=y;i+=x)					//a=i,b=ji/i
                if(ji%i==0&&gcd(i,ji/i)==x)		//由分析知,a为ji的因子
                	ans++;
            printf("%d\n",ans);
        }
    }
    return 0;
}

nefu 1221 人见人爱gcd

Description
x+y=a,lcm(x,y)=b;已知a和b求解x2+y2
Input
多组数据输入。
第一行一个t表示a,b数对的数量。
接下来t行2个数表示a,b
T<=100000
a,b<109;
Output
每组样例输出t行每行一个数表示x2+y2;
Sample Input
2
6 4
6 3
Sample Output
20
18

已知x+y,求x2+y2,自然想到把x*y表达出来
那么gcd(a,b)=gcd(x+y,lcm(x,y))=lcm(gcd(x+y,x),gcd(x+y,y))=lcm(gcd(x,y),gcd(x,y))=gcd(x,y)
上步见前面gcd的一些性质
又x·y=gcd(x,y)·lcm(x,y)=gcd(a,b)·b
令c=gcd(a,b)
则x2+y2=(x+y)2-2·x·y=a2-2·b·c

#include 
using namespace std;
int gcd(int a,int b)
{return b ? gcd(b,a%b) : a;}
int a,b,c,jie;
int main()
{
    int t;
    while(~scanf("%d",&t)){
        while(t--){
            scanf("%d%d",&a,&b);
            c=gcd(a,b);				//把c求出来
            jie=a*a-2*b*c;			//直接套用结论即可
            printf("%d\n",jie);
        }
    }
    return 0;
}

nefu 1669 高木同学的因子

Description
今天西片同学又被高木同学捉弄了,高木同学跟西片同学玩了这么一个游戏。两人心中分别想一个数字,这两个数字分别为x和y(1<=x,y<=1018),然后让西片同学说出一共有多少个整数既是x的因子,又是y的因子。由于西片和高木很有默契,所以保证他们两个想的数x和y的最大公因数不会超过109。这个问题又难住了西片同学了,你能帮帮西片同学告诉他答案吗?
Input
单组输入
数据占一行,包含两个整数x和y(1<=x,y<=1018),保证gcd(x,y)<=109
Output
输出既是x因子又是y因子的整数的个数。输出占一行
Sample Input
12 36
Sample Output
6

本题的意思是求x,y的最大公约数的因子个数
若n=p1a1p2a2…pkak,则n的因子的个数是(1+a1)*(1+a2)…(1+ak)

#include 
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b)
{return b ? gcd(b,a%b) : a;}
ll z;
ll geshu()	//求z的因子的个数
{
    ll ans=1;
    for(ll i=2;i*i<=z;i++){
        if(z%i==0){				//找到z的第一个素因子
            ll tmp=1;
            while(z%i==0){		//将这个素因子除干净
                tmp++;
                z/=i;
            }
            ans*=tmp;			//这里是乘上(1+含i的次方数)
        }
    }
    if(z>1) ans*=2;				//防止z为质数,若z为质数,则乘上2
    return ans;
}
int main()
{
    ll x,y;
    while(cin>>x>>y){
        z=gcd(x,y);				//算出x和y的最大公约数
        printf("%lld\n",geshu());
    }
    return 0;
}

快速幂取模

给你一个数a,让你求其b次连乘后的结果
当b很小时,一般的循环算法可以解决这个问题(O(B)),但是当b较大时呢
要知道1018以上,就会long long int 也可能会溢出
而在数论方面这些数又该如何表示?怎样存储?
所以在此我们定义一个模数mod来代替输出
即ab=k1*mod+t即t=ab%mod
对于这样的线性问题,我们将采用分治的思想来解决这种问题
当采用计算机语言时
ab=((a(b/n))n)·a(b%n)
当n=2时
ab=(a(b/2))·(a(b/2))·a(b%2)
(ab)%mod=(a(b/2))%mod·(a(b/2))%mod·a(b%2)%mod
而后对a(b/2)进行相同的操作

代码有两种:递归和非递归

ll quickmod(ll a,ll b,ll c)
{
    if(b==1)
        return a%c;
    if(!(b&1))		//等同于b%2==0
    {
        ll t=quickmod(a,b/2,c);
        return t*t%c;
    }
    else			//等同于b%2==1
    {
        ll t=quickmod(a,b/2,c);
        return t*t%c*a%c;
    }
}
ll quickmod(ll a,ll b,ll c)
{
    int ret=1;
    while(b)
    {
        if(b&1)		//等同于b%2==1
            ret=ret*a%c;
        a=a*a%c;
        b/=2;
    }
    return ret;
}

nefu 601 快速幂取模

Description
给定A,B,C,计算AB%C
Input
输入数据有多组,每组数据一行,有3个正整数分别为A,B和C,1<=A,B,C<=1000000000
Output
输出AB%C的值
Sample Input
2 3 5
8 2 10
Sample Output
3
4

#include 
using namespace std;
typedef long long ll;			//为long long起别称,可减少码量
ll quickmod(ll a,ll b,ll c)		//标准代码
{
    int jie=1;
    while(b){
        if(b&1)
            jie=jie*a%c;
        a=a*a%c;
        b/=2;
    }
    return jie;
}
ll a,b,c;
int main()
{
    while(cin>>a>>b>>c){
        printf("%d\n",quickmod(a,b,c));
    }
    return 0;
}

nefu 1666 库特的数学题

Description
库特很喜欢做各种高深莫测的数学题,一天,她在书上看到了这么一道题。a[1]=6,a[2]=18,a[n]=2a[n-1]+3a[n-2](n>=3),对于给出的某个数字n,求a[n]。库特一想这道题太简单了,可是看到n的范围是(n<=1e18),对于这么大范围的数,库特不知道该怎么做了,聪明的你,快来帮帮库特解决这个问题吧。(由于答案可能很大,请将答案对1e9+7(即1000000007)取模)。
Input
一个整数n(1<=n<=1e18)
Output
a[n]对1e9+7取模后的答案
Sample Input
5
Sample Output
486

这道题有两个思路
1.多看几项
a[1]=6,a[2]=18,a[3]=54,a[4]=162
发现后一项为前一项的3倍,确定a[n]=2·3n
2.用特征方程法求数列的通解
a[n]=2a[n-1]+3a[n-2]对应的特征方程为x2=2x+3
解得x=-1或x=3,则a[n]=c1·(-1)n+c2·(3)n
将a[1]=6及a[2]=18代入得c1=0,c2=2
故a[n]=2·3n

#include 
using namespace std;
typedef long long ll;
ll quickmod(ll a,ll b,ll c)		//标准代码
{
    ll jie=1;
    while(b){
        if(b&1)
            jie=jie*a%c;
        a=a*a%c;
        b/=2;
    }
    return jie;
}
int main()
{
    ll n;
    while(cin>>n){
        ll jie=quickmod(3,n,1000000007);
        cout<<2*jie%(1000000007)<<endl;
    }
    return 0;
}

nefu 1834 异或方程解的个数

Description
ljw这几天给大一同学讲课的时候,觉得自己太菜了,于是他做了一道无聊的数学题,
但是他觉得这题太难了,所以他把问题交给了聪明的你,你能帮他解决这个问题吗?
问题如下:
给你一个方程:n−(n^x)−x=0 (其中 ^ 表示两个数异或,并不是表示幂次符号哦)
现在给你n的值,问有多少种x的取值使得方程成立(数据保证n在2的30次方范围内)
Input
多组输入数据。
每组输入数据包含一个整数n,含义如题。
Output
每组输入数据输出一个整数,表示解的个数。
Sample Input
0
2
1073741823
Sample Output
1
2
1073741824

将方程整理得n=x+(n^x),然后看二进制下的某一位,讨论课发现规律:
n x+(n^x)
1 0+(1^0)=1
1 1+(1^1)=1
0 0+(0^0)=0
0 1+(0^1)=10
可以发现,第四种情况(n=0,x=1)会改变进位,故第四种情况不符合题意,排除
综合第三,第四种情况,如果n=0,那么x必为0
综合第一,第二种情况,如果n=1,x可以为0,也可以为1
故求出x在二进制下有几个1,结果就为2的几次方

#include 
using namespace std;
int main()
{
    int n,ans ;
    while(cin>>n){
        ans=0;
        for(int j=0;j<=30;j++)	//找到n的二进制数的1的数量
            if(n&(1<<j))
                ans++;
        int jie=1;
        while(ans--)			//求出2的ans次方
            jie*=2;
        cout << jie << endl;
    }
    return 0;
}

你可能感兴趣的:(大一ACM寒假培训)