题意
求前1~n每个数的分解的非平方因子数乘积方式的和
https://nanti.jisuanke.com/t/30999
思路
用修改的线性筛做
a[i]表示数i的分解方法数,对每个数多试几组数据可以发现:
1.质数的分解方法数始终为2;
2.合数的分解方法数为其两因子数的分解方法数的乘积
若该合数为平方数,则分解方法数要除4
若该合数的其中一个因子是n次方数(n≥3),则分解方法数为0
由于这题数据非常多(2e7),所以用线性筛不会超时,随着线性筛的进行顺便就把每个数的分解方法数都加上去了,这样时间复杂的还是O(n)
还是要对线性筛的原理及其筛的具体细节熟悉才行
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2e7+7;
int a[N];
int prime[N];
int sum[N];
int main(){
int index = 0;
a[1]=sum[0]=sum[1]=1;
for(int i=2;i<N;i++)//初始化
a[i]=-1;
for(int i=2;i<N;i++){
if(a[i]==-1){
a[i]=2;
prime[index++] = i;
}
for(int j = 0; j < index && prime[j] * i < N; j++){
a[i * prime[j]] = a[i]*a[prime[j]];
if(i%prime[j]==0) //含平方数的情况
a[i*prime[j]]/=4;
if(i%(prime[j]*prime[j])==0) //含n次方数(n≥3)的情况
a[i*prime[j]]=0;
if(i % prime[j] == 0){
break;
}
}
sum[i]=sum[i-1];
sum[i]+=a[i];
}
int T;
cin>>T;
while(T--){
int n;
cin>>n;
cout<<sum[n]<<endl;
}
return 0;
}
附线性筛一般模板:
//Mark数组存该下标是否为素数,值为0为素数,值为1为合数
int Mark[Max];
int prime[Max];
//判断是否是一个素数 Mark 标记数组 index 素数个数
int Prime(){
int index = 0;
memset(Mark,0,sizeof(Mark));
for(int i = 2; i < Max; i++){
//如果未标记则得到一个素数
if(Mark[i] == 0){
prime[index++] = i;
}
//标记目前得到的素数的i倍为非素数
for(int j=0; j<index && prime[j]*i < Max; j++){
Mark[i * prime[j]] = 1;
if(i % prime[j] == 0){
break;
}
}
}
return index; //返回值为素数个数,其实不太需要。。
}
线性筛 时间复杂度:O(n)
这种方法比较好理解,初始时,假设全部都是素数,当找到一个素数时,显然这个素数乘上另外一个数之后都是合数
把这些合数都筛掉,即算法名字的由来。但仔细分析能发现,这种方法会造成重复筛除合数,影响效率。
比如10,在i=2的时候,k=2*15筛了一次;在i=5,k=5*6 的时候又筛了一次。所以,也就有了快速线性筛法。
利用了每个合数必有一个最小素因子。每个合数仅被它的最小素因子筛去正好一次。所以为线性时间。
代码中体现在:
if(i%prime[j]==0)break;
prime数组 中的素数是递增的,当 i 能整除 prime[j],那么 i*prime[j+1] 这个合数肯定被 prime[j] 乘以某个数筛掉。
因为i中含有prime[j], prime[j] 比 prime[j+1] 小。接下去的素数同理。所以不用筛下去了。
在满足i%prme[j]==0这个条件之前以及第一次满足改条件时,pr[j]必定是pr[j]*i的最小因子(可以自己输出一下prime[j]中每个数的值,会发现i(或者是prime[j]的值)比j要大很多)