算法积累:数论

1.欧几里德算法/辗转相除法: 求最大公约数

时间复杂度:log n

原理:(a,b)=(b,a%b)   (表示求最大公约数)

证明如下:

    a可以表示为kb+r,假设(b,a%b)=d,那么d既是b的倍数,又是r的倍数,因此d是a的倍数

因此(a,b)>=(b,a%b)

   假设(a,b)=d,那么既是a的倍数,又是b的倍数,因此d一定是r的倍数,因此(a,b)<=(b,a%b)

   所以(a,b)=(b,a%b)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int gcd(int a,int b)//求最大公约数
{
  return b?gcd(b,a%b):a;
}

int main()
{
   int a,b;
   cin>>a>>b;
   cout<<gcd(a,b)<<endl;

   return 0;
}

补充:辗转相减法/更相减损术(不常用)

时间复杂度:O (n)

代码:

int gcd(int x, int y) 
{

	if (x == y)
	  return x;
    if(x<y) swap(x,y);
	return gcd(y, x - y);
}

例题:

                                                                      最大比例

X星球的某个大奖赛设了 M 级奖励。

每个级别的奖金是一个正整数。

并且,相邻的两个级别间的比例是个固定值。

也就是说:所有级别的奖金数构成了一个等比数列。

比如:16,24,36,54其等比值为:3/2。

现在,我们随机调查了一些获奖者的奖金数。

请你据此推算可能的最大的等比值。

输入格式

第一行为数字 N ,表示接下的一行包含 N 个正整数。

第二行 N 个正整数 Xi,用空格分开,每个整数表示调查到的某人的奖金数额。

输出格式

一个形如 A/B 的分数,要求 A、B 互质,表示可能的最大比例系数。

数据范围

0<N<100
0<Xi<1012
数据保证一定有解。

输入样例1:

3
1250 200 32

输出样例1:

25/4

输入样例2:

4
3125 32 32 200

输出样例2:

5/2

输入样例3:

3
549755813888 524288 2

输出样例3:

4/1

 代码:
 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

typedef long long LL;
const int N=110;

int n;
LL d[N];
LL a[N],b[N];

LL gcd(LL a,LL b)
{
    return b?gcd(b,a%b):a;
}

LL gcd_sub(LL a,LL b)
{
    if(a<b) swap(a,b);
    if(b==1) return a;
    return gcd_sub(b,a/b);
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%lld",&d[i]);

    sort(d,d+n);
    int cnt=0;
    for(int i=1;i<n;i++)
    {
        if(d[i]!=d[i-1])
        {
            LL r=gcd(d[i],d[0]);//为了把分子、分母分开
            a[cnt]=d[i]/r;
            b[cnt]=d[0]/r;
            cnt++;
        }
    }
    LL down=a[0],up=b[0];//分子、分母
    for(int i=1;i<cnt;i++)//分别求分子、分母的最大公约数
    {
       up=gcd_sub(up,b[i]);
       down=gcd_sub(down,a[i]);
    }
     printf("%lld/%lld\n",down,up);
     return 0;
}

 做法:

1.每一项除第一项消掉a1,利用每一项和a1的最大公约数,分离第一步求到的数的分子和分母

2.分别求分子分母的最大公约数,用辗转相减法(涉及到指数)较为方便

2.算术基本定理(公理):

所有正整数N一定可以唯一地分解成若干个质数因子乘积的形式。

3.筛法求素数——线性筛法

时间复杂度: O(n)

功能:求出1~n中的所有质数及每个数的最小质因子

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=(1<<20)+10;
int n;
int primes[N],cnt;//存1~n中的所有质数
bool st[N];//当前数有没有被访问过
int minp[N];//当前数的最小质因子

void get_primes(int n)
{
  for(int i=2;i<=n;i++)
  {
    if(!st[i]) minp[i]=i,primes[cnt++]=i;
    for(int j=0;primes[j]*i<=n;j++)
    {
      st[primes[j]*i]=true;
      minp[primes[j]*i]=primes[j];
      if(i%primes[j]==0) break;
    }
  }
}
int main()
{
   get_primes(100000);
   for(int i=0;i<20;i++) printf("%d\n",primes[i]);
   return 0;
}

 get_primes函数筛掉了所有合数,并用其最小质因子筛掉的。解释:“if(i%primes[j]==0) break;”说明枚举的质数primes[j]<=i的最小质因子,而primes[j]和i都是从小到大枚举的,所以primes[j]*i的最小质因子一定是min{primes[ j ],i的最小质因子},因此primes[ j ]就是当前筛掉的合数primes[j]*i的最小质因子。

例题(应用2、3):

                                                                1295. X的因子链

输入正整数 XX,求 XX 的大于 11 的因子组成的满足任意前一项都能整除后一项的严格递增序列的最大长度,以及满足最大长度的序列的个数。

输入格式

输入包含多组数据,每组数据占一行,包含一个正整数表示 XX。

输出格式

对于每组数据,输出序列的最大长度以及满足最大长度的序列的个数。

每个结果占一行。

数据范围

1≤X≤2201≤X≤220

输入样例:

2
3
4
10
100

输出样例:

1 1
1 1
2 1
2 2
4 6

做法:

1.由算数基本定理知,X一定可以唯一地分解成若干个质数因子乘积的形式。

因此从X的所有质数因子中选取若干相乘,一定也是X的因子,最大的因子就是所有质数因子相乘。

2.要想构成的最长序列,后一项/前一项一定是质数。

假设序列第一个数是某个质数因子,最后一个项是所有质数因子相乘,这样的序列最长。

 最长的序列中有a1+a2+a3...+a[k]个数。

3.满足最大长度的序列的个数与质数因子的全排列有关。

 因为每次乘上去的质数因子可以重复,因此还要除(每个质数因子重复次数阶乘的乘积)

4.所以求出X的质数因子的个数,及其重复出现次数即可。

 代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N=(1<<20)+10;
int primes[N],cnt;
int minp[N];
bool st[N];

void get_primes(int n)
{
    for(int i=2;i<=n;i++)
    {
       if(!st[i]) minp[i]=i,primes[cnt++]=i;
       for(int j=0;primes[j]*i<=n;j++)
       {
           st[primes[j]*i]=true;
           minp[primes[j]*i]=primes[j];
           if(i%primes[j]==0) break;
       }
    }
}

int main()
{
    get_primes(N-1);
    int fact[30],sum[N];
    int x;
    while(scanf("%d",&x)!=-1)
    {
        int k=0,tot=0;
        while(x>1)
        {
            int p=minp[x];
            fact[k]=p,sum[k]=0;
            while(x%p==0)
            {
                x/=p;
                sum[k]++;
                tot++;
            }
            k++;
        }
        LL res=1;
        for(int i=1;i<=tot;i++) res*=i;
        for(int i=0;i<k;i++)
          for(int j=1;j<=sum[i];j++)
            res/=j;
        printf("%d %lld\n",tot,res);
    }
    return 0;
}

4.公式:求约数之和以及约数个数

 假设d是N的一个约数,

例题:

                                                                  聪明的燕姿

城市中人们总是拿着号码牌,不停寻找,不断匹配,可是谁也不知道自己等的那个人是谁。

可是燕姿不一样,燕姿知道自己等的人是谁,因为燕姿数学学得好!

燕姿发现了一个神奇的算法:假设自己的号码牌上写着数字 SS,那么自己等的人手上的号码牌数字的所有正约数之和必定等于 SS。

所以燕姿总是拿着号码牌在地铁和人海找数字(喂!这样真的靠谱吗)。

可是她忙着唱《绿光》,想拜托你写一个程序能够快速地找到所有自己等的人。

输入格式

输入包含 kk 组数据。

对于每组数据,输入包含一个号码牌 SS。

输出格式

对于每组数据,输出有两行。

第一行包含一个整数 m,表示有 m 个等的人。

第二行包含相应的 m 个数,表示所有等的人的号码牌。

注意:你输出的号码牌必须按照升序排列。

数据范围

1≤k≤100,
1≤S≤2×10^9

输入样例:

42

输出样例:

3
20 26 41

 代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=50000;
int primes[N],cnt;
bool st[N];

int ans[N],len;
int s;

void get_primes(int x)
{
    for(int i=2;i<=x;i++)
    {
        if(!st[i]) primes[cnt++]=i;
        for(int j=0;primes[j]*i<=x;j++)
        {
            st[primes[j]*i]=true;
            if(i%primes[j]==0) break;
        }
    }
}

bool is_prime(int x)
{
   if(x<N) return !st[x];
   for(int i=0;primes[i]<=x/primes[i];i++)
     if(x%primes[i]==0)
       return false;
   return true;
}

void dfs(int last,int prod,int s)//参数分别为上一个质数的下标,当前结果值,s的剩余值
{
    if(s==1)
    {
        ans[len++]=prod;
        return;
    }
    if(s-1>(last<0?1:primes[last])&&is_prime(s-1))
      ans[len++]=prod*(s-1);
    for(int i=last+1;primes[i]<=s/primes[i];i++)//i表示枚举的质数,j表示该乘积项的和,t表示该乘积项中幂最大的数
    {
        int p=primes[i];
        for(int j=1+p,t=p;j<=s;t*=p,j+=t)
          if(s%j==0)
            dfs(i,prod*t,s/j);
    }
}

int main()
{
    get_primes(N-1);

    while(cin>>s)
    {
        len=0;
        dfs(-1,1,s);
        
        cout<<len<<endl;
        if(len)
        {
            sort(ans,ans+len);
            for(int i=0;i<len;i++) 
              cout<<ans[i]<<' ';
            cout<<endl;
        }
    }
    return 0;
}

5.裴蜀定理——扩展欧几里得算法

时间复杂度:log n

定理:1.如果(a,b)=d,那么存在x,y使得ax+by=d

            2.若(a,b)=d,存在ax+by=d',那么d'是d的倍数(不常用)

扩展欧几里得算法:求出这样的一组x0,y0

令a'=a/d,b'=b/d;

所有的解都可以用如下形式表示:

 x=x0+kb'

 y=y0-ka'

算法原理:

 如此令 y=y-a/b*x递归求解。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

int exgcd(int a,int b,int& x,int& y)
{
    if(!b)
    {
      x=1,y=0;
      return a;
    }
    int d=exgcd(b,a%b,y,x);
    y-=a/d*x;
    return d;
}

int main()
{
   int a,b,x,y;
   cin>>a>>b;
   int d=exgcd(a,b,x,y);

   printf("%d*%d+%d*%d=%d\n",a,x,b,y,d);

   return 0;
}

例题:

                                                                            五指山

大圣在佛祖的手掌中。

我们假设佛祖的手掌是一个圆圈,圆圈的长为 n,逆时针记为:0,1,2,…,n−1,而大圣每次飞的距离为 d。

现在大圣所在的位置记为 x,而大圣想去的地方在 y。

要你告诉大圣至少要飞多少次才能到达目的地。

注意:孙悟空的筋斗云只沿着逆时针方向翻。

输入格式

有多组测试数据。

第一行是一个正整数 T,表示测试数据的组数;

每组测试数据包括一行,四个非负整数,分别为如来手掌圆圈的长度 n,筋斗所能飞的距离 d,大圣的初始位置 x 和大圣想去的地方 y。

输出格式

对于每组测试数据,输出一行,给出大圣最少要翻多少个筋斗云才能到达目的地。

如果无论翻多少个筋斗云也不能到达,输出 Impossible。

数据范围

2<n<10^9,
0<d<n
0≤x,y<n

输入样例:

2
3 2 0 2
3 2 0 1

输出样例:

1
2

 做法:

易知 (x+bd)%n=y

因此 x+bd=y+an

a,b为未知数,联想到裴蜀定理

整理为   -an+bd=y-x

若y-x是(n,d)的倍数,则有解,且b=b0-kn/(n,d)

b=mn/(n,d),要使b最小,令b=b mod n/(n,d);

若y-x不是(n,d)的倍数,则无解;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long LL;

LL exgcd(LL a,LL b,LL& x,LL& y)
{
    if(!b)
    {
        x=1,y=0;
        return a;
    }
    LL d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        LL n,d,x,y,a,b;
        scanf("%lld%lld%lld%lld",&n,&d,&x,&y);
        int gcd=exgcd(n,d,a,b);
        if((y-x)%gcd!=0) puts("Impossible");
        else
        {
            b*=(y-x)/gcd;
            n/=gcd;
            printf("%lld\n",(b%n+n)%n);
        }
    }
    return 0;
}

6.组合数有关定理

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值