约数(又称因数)

约数

唯一分解定理:任何一个大于1的数都可以被分解成有限个质数乘积的形式 $N=\prod_{i=1}{m}p_{i}{c_i} $

N N N的正约数个数为: ( c 1 + 1 ) × ( c 2 + 1 ) × . . . × ( c m + 1 ) = ∏ i = 1 m ( c i + 1 ) (c_1+1)\times (c_2+1)\times ...\times (c_m+1)=\prod_{i=1}^{m}(c_i+1) (c1+1)×(c2+1)×...×(cm+1)=i=1m(ci+1)
N M N^M NM的正约数个数为: ( M × c 1 + 1 ) × ( M × c 2 + 1 ) × . . . × ( M × c m + 1 ) = ∏ i = 1 m ( M × c i + 1 ) (M\times c_1+1)\times (M\times c_2+1)\times ...\times (M\times c_m+1)=\prod_{i=1}^{m}(M\times c_i+1) (M×c1+1)×(M×c2+1)×...×(M×cm+1)=i=1m(M×ci+1)
N N N的所有正约数之和为: σ ( n ) = ( 1 + p 1 + p 1 2 + . . . + p 1 c 1 ) × . . . × ( 1 + p m + p m 2 + . . . + p m c m ) \sigma (n)=(1+p_1+p_{1}^{2}+...+p_{1}^{c_1})\times ...\times (1+p_m+p_{m}^{2}+...+p_{m}^{c_m}) σ(n)=(1+p1+p12+...+p1c1)×...×(1+pm+pm2+...+pmcm) (每一块乘积是一个等比数列)

随机数据下,约数个数的期望是 O ( ln ⁡ n ) O(\ln_{}{n}) O(lnn)。一个数的约数个数上界是 2 N 2\sqrt N 2N

例题:

LightOJ-1336 Sigma Function

题意:求 1 ∼ n 1\sim n 1n中, σ ( n ) \sigma (n) σ(n)是偶数的数的个数。
思路:(主要要自己去推导分析)根据公式: σ ( n ) = ( 1 + p 1 + p 1 2 + . . . + p 1 c 1 ) × . . . × ( 1 + p m + p m 2 + . . . + p m c m ) \sigma (n)=(1+p_1+p_{1}^{2}+...+p_{1}^{c_1})\times ...\times (1+p_m+p_{m}^{2}+...+p_{m}^{c_m}) σ(n)=(1+p1+p12+...+p1c1)×...×(1+pm+pm2+...+pmcm),当乘积的每一项都为奇数时, σ ( n ) \sigma(n) σ(n)是奇数。当 p = 2 p=2 p=2时, c c c为任意值;当 p ! = 2 p!=2 p!=2时, c c c为偶数 时, σ ( n ) \sigma(n) σ(n)为奇数。当2及其它质数的指数都取偶数时(即平方数),有 n \sqrt n n 个数;当2的指数取奇数,其他质数的指数取偶数时,有 n / 2 \sqrt{n/2} n/2 个。

#include <bits/stdc++.h>
using namespace std;

#define int long long
int T,n,cs;

signed main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		int ans=n;
		ans-=(int)sqrt(n);
		ans-=(int)sqrt(n/2);
		printf("Case %lld: %lld\n",++cs,ans);
	}
}
求一个数的正约数

复杂度: O ( N ) O(\sqrt N) O(N ).

for(int i=1;i*i<=n;++i){
	if(n%i==0){
		printf("%d ",i);
		if(n/i!=i) printf("%d ",n/i);
	}
}

如果能知道一个大数(1e18)的质因子分解情况,可以直接dfs枚举幂次,搞出这个大数的所有因子,效率更快!

例题:E2. Divisible Numbers (hard version)

多次求解一个很大的数的正约数个数

比一个一个 O ( N ) O(\sqrt N) O(N )去求解要更快。

//先欧拉筛筛出 1~根号n 的质因子 
void prime(int n){
	for(int i=2;i<=n;++i){
		if(!vis[i]){
			vis[i]=1;
			pri[++cnt]=i;
		}
		for(int j=1;pri[j]<=n/i;++j){
			vis[pri[j]*i]=1;
			if(i%pri[j]==0) break;
		}
	}
}

//然后利用定理结论 计算正约数的个数 
//根据筛出来的质数对数n进行质因数分解
int cal(int n){
	int ans=1;
	for(int i=1;i<=cnt;++i){
		if(pri[i]>n) break;
		if(n%pri[i]==0){
			int num=0;
			while(n%pri[i]==0) num++,n/=pri[i];
			ans*=(num+1);
		}
	}
	if(n>1) ans*=2;
	return ans;
} 

例题:
LightOJ-1341 Aladdin and the Flying Carpet
题意:多组输入,每次给定 a , b a,b a,b,求解 a a a的因子数,要求因子大于 b b b,且小于等于 ( a ) \sqrt(a) ( a),若 ( a ) × ( a ) = = a \sqrt(a)\times \sqrt(a)==a ( a)×( a)==a ( a ) \sqrt(a) ( a)不取。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N=1e6+10;
int cnt=0,pri[N];
bool vis[N];

void prime(int n){
	for(int i=2;i<=n;++i){
		if(!vis[i]){
			vis[i]=1;
			pri[++cnt]=i;
		}
		for(int j=1;pri[j]<=n/i;++j){
			vis[pri[j]*i]=1;
			if(i%pri[j]==0) break;
		}
	}
}

int cal(int n){
	int ans=1;
	for(int i=1;i<=cnt;++i){
		if(pri[i]>n) break;
		if(n%pri[i]==0){
			int num=0;
			while(n%pri[i]==0) num++,n/=pri[i];
			ans*=(num+1);
		}
	}
	if(n>1) ans*=2;
	return ans/2;
} 

int T,a,b,cs;

signed main(){
	prime(1000000);
	scanf("%lld",&T);
	while(T--){
		scanf("%lld%lld",&a,&b);
		if(b*b>=a) printf("Case %lld: %lld\n",++cs,0ll);
		else{
			int ans=cal(a);
			for(int i=1;i<b;++i) if(a%i==0) ans--;
			printf("Case %lld: %lld\n",++cs,ans);	
		}
	}
}
1 ∼ n 1\sim n 1n中每个数的正约数集合

时间复杂度: O ( n log ⁡ n ) O(n\log_{}{n}) O(nlogn)
倍数法的思路是,对于每个数d,它的倍数 2 d , 3 d , 4 d … … 2d,3d,4d…… 2d,3d,4d……等等的约数里就有 d d d。所以时间复杂度为 O ( n + n 2 + n 3 + . . + n n ) ≈ O ( n log ⁡ n ) O(n+\frac{n}{2} +\frac{n}{3} +..+\frac{n}{n})\approx O(n\log_{}{n}) O(n+2n+3n+..+nn)O(nlogn)
常用与一些于因子有关的计算,如计算 $\sum_{i=1}^{n} \sum_{d|n} d $ 或者 ∑ i = 1 n ∑ d ∣ n f ( d ) \sum_{i=1}^{n} \sum_{d|n} f(d) i=1ndnf(d).

#include <bits/stdc++.h>
using namespace std;

int n;
vector<int> factor[100000];

int main(){
	cin>>n;
	for(int i=1;i<=n;++i) {
		for(int j=1;j*i<=n;++j) { //1倍的i,2倍的i,3倍的i,...,都有i
			factor[i*j].push_back(i); //因子从小到大被求出 //写成 factor[i*j].push_back(j)的话,因子从大到小被求出 
		}
	}
    /*另一种写法
    for(int i=1;i<=n;i++){ 
        for(int j=i;j<=n;j+=i){ //1倍的i,2倍的i,3倍的i,...,都有i
            factor[j].push_back(i);
        }
    }
    */
	for(int i=1;i<=n;++i) {
		cout<<i<<": ";
		for(int j=0;j<factor[i].size();++j) cout<<factor[i][j]<<' ';
		cout<<endl;
	}
}
1 ∼ n 1\sim n 1n中每个数的质因子集合

欧拉筛中求得

vector<int> v[N];
int cnt=0,pri[N];
bool vis[N];

void prime(int n){
	for(int i=2;i<=n;++i){
		if(!vis[i]){
			pri[++cnt]=i;
			v[i].pb(i);
			vis[i]=true;
		}
		for(int j=1;pri[j]<=n/i;++j){
			vis[pri[j]*i]=true;
			v[pri[j]*i]=v[i];
			if(i%pri[j]==0) break;
			v[pri[j]*i].pb(pri[j]);
		}
	}
}

相关例题:
Acwing-97.约数之和
求解 A B A^B AB的所有约数之和, 0 < = A , B < = 5 e 7 0<=A,B<=5e7 0<=A,B<=5e7.
做法:质因数分解 A A A,然后运用 约数之和公式+等比数列公式 求解。
有一个细节说明:
在求乘法逆元时,当p[i]-1是MOD的倍数时,即 p [ i ] p[i] p[i]不存在MOD的逆元,即 p [ i ] − 1 ≡ 0 ( m o d M O D ) p[i]-1\equiv 0\pmod{MOD} p[i]10(modMOD),即 p [ i ] ≡ 1 ( m o d M O D ) p[i]\equiv 1\pmod{MOD} p[i]1(modMOD),所以 1 + p i + p i 2 + . . . + p i c i ≡ 1 + 1 + 1 + . . . + 1 ≡ c i ∗ b + 1 ( m o d M O D ) 1+p_i+p_{i}^{2}+...+p_{i}^{c_i}\equiv 1+1+1+...+1\equiv c_i*b+1 \pmod{MOD} 1+pi+pi2+...+pici1+1+1+...+1cib+1(modMOD).

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N=1e2;
const int MOD=9901;
int a,b;
int p[N],c[N],cnt=0;

void divide(int x){
	for(int i=2;i<=sqrt(x);++i){
		if(x%i==0) p[++cnt]=i;
		while(x%i==0){
			x/=i;
			c[cnt]++;
		}
	}
	if(x>1) p[++cnt]=x,c[cnt]=1;
}

int qpow(int a,int b){
	int ans=1;
	while(b){
		if(b&1) ans=ans*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return ans;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>a>>b;
	int ans=1;
	divide(a);
	for(int i=1;i<=cnt;++i){
		if((p[i]-1)%MOD==0){
			ans=(c[i]*b+1)*ans%MOD;
		}
		else{
			ans=(qpow(p[i],c[i]*b+1)-1+MOD)%MOD*qpow(p[i]-1,MOD-2)%MOD*ans%MOD; //指数不能取mod 
		}
	}
	if(a) cout<<ans<<'\n'; //卡0点 
	else cout<<0<<'\n';
}
  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值