目录
个人介绍
本博客的概况
因数
考法
考法一:通常题干中会要求我们来求某一个数据的因数的个数
考法二:【1,n】(n/i)就是【1,n】的范围内i作为约数的次数——当你看到下面的数论分块你就知道这个性质的厉害了。
朴素解法
优化做法
例题
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int mod=1e9+7;
typedef long long ll;
ll ans;
int d[N];
int main()
{
int n;cin>>n;
for(int i=1;i<=N;i++)
{
for(int j=i;j<=N;j+=i)
{
d[j]++;
}
}
for(int i=1;i<=n;i++)
{
ans=(ans+d[i])%mod;
}
cout<<ans;
return 0;
}
筛质数
前言:
对于初学者来讲,只要理解了欧拉筛,那么埃氏筛,朴素筛法。其实吧,与其讲是筛质数,不如讲是筛合数,把合数踢出来,余下的就是质数了。
原理:
引入的知识:
唯一分解定理:任何一个大于1的自然数 N,如果N不为质数,**那么N可以唯一分解成有限个质数的乘积。比如12可以分解为2,2,3的乘积,8可以分解为2,2,2的乘积。
具体的阐述:
由于一个数可以被分解为若干个质数的乘积,那么欧拉筛的精髓就在于它保障了每个被筛出来的合数是被它的最小因数和另一个因数的乘积筛掉的。关键词“最小因数”
代码模板:
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int cnt;
int p[N];//存储质数的数组
bool vis[N];//vis[i]判断的是i是否为质量,为1的话为合数,为0的话为质数
void ola(int n)
{
for(int i=2;i<=n;i++)
{
if(vis[i]==0) p[++cnt]=i;
for(int j=1;j<=cnt&&i*p[j]<=n;j++)
{
vis[i*p[j]]=1;
if(i%p[j]==0) break;//这一步也是许多人看不懂的,但他就是保障筛掉的合数是最小质数和令一个因数筛出的关键步骤
}
}
}
int main()
{
int n;
cin>>n;
ola(n);
cout<<cnt<<endl;
cout<<p[2]<<endl;
return 0;
}
难点的解释:
(1):关于模板中的两层循环:筛出合数的方式可以理解为"c=a*b"的方式来筛出的,第二层p[j]可以理解为筛出质数的最小质数a,第一层i可以理解为筛的是因数b。
(2):关于步骤“if (i%p[j]==0) break”:如果没有这一个步骤的话,以12为例,12就会被3*4和2*6重复筛掉,而加了这一步就可以保障在即将被3*4筛掉的时候,由于4可以被p[1]即2整除,提前阻止重复筛的现象,达到最优的时间复杂度。
例题:
P3912 素数个数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int cnt;
int p[N];//存储质数的数组
bool vis[N];//vis[i]判断的是i是否为质量,为1的话为合数,为0的话为质数
void ola(int n)
{
for(int i=2;i<=n;i++)
{
if(vis[i]==0) p[++cnt]=i;
for(int j=1;j<=cnt&&i*p[j]<=n;j++)
{
vis[i*p[j]]=1;
if(i%p[j]==0) break;//这一步也是许多人看不懂的,但他就是保障筛掉的合数是最小质数和令一个因数筛出的关键步骤
}
}
}
int main()
{
int n;
cin>>n;
ola(n);
cout<<cnt<<endl;
return 0;
}
最大公约数
代码模板:
int gcd(int a,int b)
{
if(b==0) return a;
return gcd(b,a%b);
}
万能的stl:
__gcd(a,b)函数非常方便,但要小心的是stl用多了可能会TLE,因为电脑从若干封装好的函数中找到__gcd(a,b)还是有点小耗时间的。
欧几里德定理
内容
例题
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int dp[N];//dp[i]表示的是i个包子可否凑出
int w[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i];
dp[0]=1;
int temp=1;
for(int i=1;i<=n;i++)
{
temp=__gcd(temp,w[i]);//这里偷懒用来stl,啊哈哈哈
}
if(temp!=1){
cout<<"INF"<<endl;//欧几里得的体现
return 0;
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<N;j++)
{
if(dp[j]==1)
{
dp[j+w[i]]=1;
}
}
}
int ans=0;
for(int i=1;i<N;i++) if(dp[i]==0) ans++;
cout<<ans;
return 0;
}
总结:之前没有把欧几里得的用法讲明白这里可以理解为给你一个数集(a,b,c,d,......),用x*a+y*b+z*c.....来凑数(x,y,z均为常数),假如这个数集的最大公约数为1,那么它不能凑出来的数据是有限的,如果这个数集的最大公约数不为1,那么它不能凑出来的数无限的。
快速幂(a^b)
原理:
代码模板:
#include<bits/stdc++.h>
using namespace std;
int quick_pow(int a,int b)//快速幂
{
int ans=1;int temp=a;
while(b)
{
if(b&1)ans=ans*temp;
temp=temp*temp;
b>>=1;
}
return ans;
}
int main()
{
int a,b;cin>>a>>b;
int result=quick_pow(a,b);
cout<<result;
return 0;
}
数论分块:
用处:
相关推导:
代码模板:
for(ll l=1,r;l<=n;l=r+1)
{
r=n/(n/i);
ans+=(n/i)*(r-i+1);
}
例题:
P1403 [AHOI2005]约数研究 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
ll ans=0;ll n;
cin>>n;
for(ll l=1,r;l<=n;l=r+1)//这里基本就是上面的死的模板了
{
r=n/(n/l);
ans+=(r-l+1)*(n/l);
}
cout<<ans<<endl;
}
分析:这里要求1~n因数个数的和,嘿嘿,记起开始我们提到的性质——(n/i)就是【1,n】的范围内i作为约数的次数。所以比如想要求(1~3)之间所有数据的约数个数之和,那么先统计(1~3)之间1出现的次数,(1~3)之间2出现的次数,(1~3)之间3出现的次数。3+1+1==5次。直接转化为问题了。然后套模板走人。