算法学习之路的心得二之数论基础

GDUT 20 寒假集训专题2.数论

  1. 总结

  2. 题目题解

快速幂取模

这是一个很好用的东西,用来计算a^b%mod,当b很大的时候目前的整数类型无法存下这么大的数字的时候,快速幂取模就显得尤为重要了,这个模板是固定的,背下来即可用了。

#define ll long long
using namespace std;
ll mod=1e9+7;
ll mod_pow(ll a,ll n)
{
		ll res=1;
		while(n>0){
			if(n&1){ //判断n是不是奇数
				res=res*a%mod;
			}
			a=a*a%mod;
			n>>=1; //相当于n/2
		}
		return res;
}

逆元

加减乘的取模都不难,但是(a/b)%mod,就要用到一个叫逆元的东西,学的时候是用费马小定理证明的,最后得到一个叫逆元公式的式子。

逆元函数 公式为 (a/b)%mod=(a*b^(mod-2))%mod

这里的mod可能会很大,这时候就要用到上面所说的快速幂取模了,结合在一起用。

ll inv(ll a,ll b)
{
    return (a*quick_pow(b,mod-2))%mod; //快速幂优化
}

质素筛

质素筛有两种,但是欧拉筛比埃氏筛更优化,所以这里只写欧拉筛

代码也是个模板,没得变的,但是如果可以灵活运用就可以降低复杂度,下面的习题中,美质素就是要灵活运用

int prime[10000000]; 
bool is_prime[10000000];
int sieve(int n)
{
		int p=0,i;
		for(i=0;i<=n;i++){
			is_prime[i]=true;
		}
		is_prime[0]=is_prime[1]=false;
		for(i=2;i<=n;i++){
			if(is_prime[i]){
				prime[p++]=i;
			}
			for(int j=0;j<p&&i*prime[j]<=n;j++){
				is_prime[i*prime[j]]=false;
				if(i%prime[j]==0){
					break;
				}
			}
		}
		return p;
}

欧几里得算法

gcd,也叫最大公约数,如果两个数的gcd等于1,那么这两个数也称为互质,下面有一道题会用到。

求gcd的方法很多,这里介绍一种高效的,叫欧几里得算法

int gcd(int a,int b)
{
		if(b==0){
			return a;
		}
		else return gcd(b,a%b);
}

扩展欧几里得算法

基于欧几里得算法,衍生出一个求线性不定方程的算法,叫扩展欧几里得算法,这个算法可以求出

ax+by=gcd(a,b),这个式子的其中一个解,

代码如下

ll exgcd(ll a,ll b,ll &x,ll &y)  
{  
    if(b==0)  
    {  
        x=1;  
        y=0;  
        return a;  
    }  
    ll ans=exgcd(b,a%b,x,y);  
    ll t=x;  
    x=y;  
    y=t-a/b*y;  
    return ans;  
} 

C(m,n)的求解

根据排列组合中c的公式,要求c是要求3个阶乘的,如果要求很多个c并且m,n都比较大的话,就要用到一个奇妙的方法 ,,开一个数组把小于n的阶乘全部记下来,当然了,一般题目会要求取模,不然n!的结果会非常非常恐怖的,用数组记下来了,要用的时候直接调用就行了。

注意,这里的n是比较大,不是非常大,仅限于10的6次方以下,如果更大的话,那就要另寻他路了。

代码如下

ll mod=1e9+7;
ll f[1000005]={1,1};   //制作阶乘模数组
for(int i=2;i<=n;i++){
	f[i]=i*f[i-1]%mod;  
}
ll c(ll i,ll n)	//计算C,使用了逆元	
{
	return f[n]*mod_pow(f[i]*f[n-i]%mod,mod-2)%mod; 
}
ans=(ans+c(i,n))%mod;   //答案也要取模 

求n的质因子

有些题目要求求出n的质因子,这里有一个高效的方法

代码如下

int a[100000];
int cai(int n) //返回个数m
{
	int m=0;
	for(int i=2;i*i<=n;i++){
		if(n%i==0){
			a[m++]=i;
			while(n%i==0){
				n/=i;
			}
		}
	}
	if(n!=1){
		a[m++]=n;
	}
	return m;
}

容斥定理

先给出代码模板,具体应用请看下面的一道习题

ll all(ll k){   // cnt 是分解的那个数的质因子的个数
    ll ans=0;
    for(ll i=1;i<(1<<cnt);i++){
        ll sum=1,num1=0,tmp=i;
        for(ll j=0;j<cnt;j++){
            if(i&(1<<j)){
                sum*=s[j];num1++;
            }
        }
        tmp=k/sum;
        if(num1&1){ans+=tmp;}
        else{
            ans-=tmp;
        }
    }
    return ans;
}

1.美素数

题目链接

题目描述:小明对数的研究比较热爱,一谈到数,脑子里就涌现出好多数的问题,今天,小明想考考你对素数的认识。问题是这样的:一个十进制数,如果是素数,而且它的各位数字和也是素数,则称之为“美素数”,如29,本身是素数,而且2+9 = 11也是素数,所以它是美素数。给定一个区间,你能计算出这个区间内有多少个美素数吗?

解题思路:这道题主要是考了线性筛,这里我用了欧拉筛,这道题看上去不算很难,目标也很明确,但是我做的时候超时了5次!!可能是我比较菜qwq(最最最主要的原因是没有在判断一个数是不是美的函数里面的那个循环没有加那个break,我一直在其他地方优化,甚至是二分都用上了!!)

我的思路是开两个质数数组,一个存真正的质数,然后在每个质数筛进去的时候,再用一个数组存美质数,最后在查区间的时候,用二分可以更快的定位到美质数中的左右,作差就是答案了。

ac代码

#include <iostream>
#include <stdio.h>
#include <algorithm>
#define ll long long 
using namespace std;
int is_prime[1000005];
int prime[500000],p;
int mei[500000],d;
bool is_mei(int i)
{
		int sum=0;
		while(i){
			sum+=i%10;
			i/=10;
		}
		int t=0;
		for(int j=0;j<p;j++){
			if(prime[j]==sum){
				t=1;
			}
			if(prime[j]>sum){ 
				break;   //罪魁祸首!!!
			}
		}
		if(t==1)return 1;
		return 0;
}
void sieve(int n)
{
		p=0;
		d=0;
		for(int i=0;i<n;i++){
			is_prime[i]=1;
		}
		for(int i=2;i<n;i++){
			if(is_prime[i]){
				prime[p++]=i;
				if(is_mei(i)){
					mei[d++]=i;
				}
			}
			for(int j=0;j<p&&prime[j]*i<n;j++){
				is_prime[prime[j]*i]=0;
				if(i%prime[j]==0){
					break;
				}
			}
		}
}
int coun(int l,int r)
{
		int *s,*f,c,p;
		s=lower_bound(mei,mei+d,l);  //二分区间
		f=upper_bound(mei,mei+d,r);
		p=f-s;
		return p;
}
int main()
{
		int t,l,r,i=1; 
		cin>>t;
		sieve(1000000);
		while(t--){
			cin>>l>>r;
			int c=coun(l,r);
			printf("Case #%d: %d\n",i++,c);
		}
		return 0;
}

2.Co-prime

题目链接

题目描述:给定一个数字N,要求你计算a和b(包括b)之间与N相对素数的整数的数目。如果两个整数除了1之外没有公约数,或者,如果它们的最大公约数是1,则称它们为协素数或相对素数。数字1对所有整数来说都是质数。

解题思路:求a到b之间与N互素的个数,这是容斥定理经典题目,也可以作为模板记起来。先把N拆了,运用上面所说的那个求N的素因子,把N的所有素因子存在数组里面,然后根据容斥定理,可以求出1 到a-1和1到b之间与N互素的个数,然后用1到b的个数减去 1到(a-1)的个数就是答案了。

ac代码

#include <iostream>
#define ll long long
using namespace std;
int a[10000];
int chai(int n)
{
		int m=0;
		for(int i=2;i*i<=n;i++){
			if(n%i==0){
				a[m++]=i;
			}
			while(n%i==0){
				n/=i;
			}
		}
		if(n!=1){
			a[m++]=n;
		}
		return m;
}
ll solve(ll n,int m)
{
		ll ans=0;
		for(int i=1;i<(1<<m);i++){
			ll sum=1,temp,num=0;
			for(int j=0;j<m;j++){
				if(i&(1<<j)){
					sum*=a[j];
					num++;
				}
			}
			temp=n/sum;
			if(1&num){
				ans+=temp;
			}
			else {
				ans-=temp;
			}
		}
		return n-ans;
}
int main()
{
		int t,i=1;
		cin>>t;
		while(t--){
			ll a,b;
			int n,m;
			cin>>a>>b>>n;
			m=cai(n);
			printf("Case #%d: %lld\n",i++,solve(b,m)-solve(a-1,m));
		}
	
		return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值