[JZOJ5594][min25筛]最大真因数

本文探讨了一个数学算法问题:计算指定区间内所有合数的最大真因数之和。通过分析与优化,介绍了一种名为min25筛的高效算法实现,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

一个合数的真因数是指这个数不包括其本身的所有因数,例如6 的正因数有
1; 2; 3; 6,其中真因数有1; 2; 3。一个合数的最大真因数则是这个数的所有真因数中最大
的一个,例如6 的最大真因数为3。
给定正整数l 和r,请你求出l 和r 之间(包括l 和r)所有合数的最大真因数之和。
这里写图片描述

分析

就是叫你算i=l..r,i  iminfactor(i)∑i=l..r,i为合数  iminfactor(i)
前80分很好拿,基本的筛法即可。
提一提7,8两档,由于我们筛的是合数,那么必定存在根号以内的质因子,我们用根号以内的质因子筛[l,r]的部分即可。
线筛是不可能优化的了,考虑枚举质数去筛的筛法,我们必须要枚举一个质数p的倍数px,是因为不知道px有没有更小的质因子,即被之前的质数筛过了。如果能够优化就好了。
先拆成[1..l-1]和[1..r]。
容斥是不可能容斥的了。分析一下,假如我们算[1..n]一个质数p能筛的倍数px,x必然是小于p的质数都没有筛掉的trunc(n/p)以内的数。那么所有x的和就是trunc(n/p)以内的没有被p筛过的数的和。
考虑一个类似洲阁筛的叫做min25筛的东西。
设f(i,j)表示[2..i]中,除去前j个质数的非自身的倍数,剩下的数的和。也就是说,质数也算在里面。设第j个质数为prime[j].
那么(f(n,j-1)-f(n,j))/prime[j],就是第j个质数筛的那些x的和。
考虑这个东西的性质,尝试快速算。
对于一个f(i,j)
如果prime[j]2>iprime[j]2>i,很明显j可以-1,因为根本不会筛掉任何数。那么j不比ii大。
否则,考虑第j个质数筛掉的x*prime[j],我们先把prime[j]除掉,那么x满足2xi/prime[j]2≤x≤i/prime[j],且x的质因子不含前j-1个质数。那么x的和就是f(iprime[j],j1)sum[j1]f(⌊iprime[j]⌋,j−1)−sum[j−1],其中sum[j-1]表示前j个质数的和。
那么有递推式f(i,j)=f(i,j1)prime[j](f(iprime[j],j1)sum[j1])f(i,j)=f(i,j−1)−prime[j]∗(f(⌊iprime[j]⌋,j−1)−sum[j−1])
记忆化搜索求f(n,m),其中prime[m]为平方后比n小的最大质数,分析时间复杂度,发现i的取值都能够表示成nx⌊nx⌋的形式,因为nab=nab⌊nab⌋=⌊⌊na⌋b⌋。那么状态数就是每个i的j的个数和。也就是i=1..nnilnni∑i=1..nniln⌊ni⌋O(n34logn)O(n34logn)左右。时间复杂度跟这个一样。而实际上,如果记忆化搜索,有的n/i是用不到的。但递推就要用到了。

由于状态数比较多,我们记忆化搜索会很慢,哈希存不下,map带log很慢,所以最好按j分层一层一层递推,具体的,把每个n/i分类,对于i<=sqrt(n)的i,我们按i为下标存编号,其余的按n/i为下标存编号,即像map一样记录映射,然后推即可。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
const int N=2e5+5,M=205,mo=1e9+7;
ll pri[N],i,j,t,sn,sum[N],tmp,ret,n,df[N],val[N],tt,le,ri,v,siz,f[2][N],s,p;
bool pd[N];
ll ans,cnt,l,r,v1,v2,x,mnf[N];
void predo(ll n)
{
    fo(i,2,n)
    {
        if (!pd[i])
        {
            pri[++pri[0]]=i;
            sum[pri[0]]=pri[pri[0]]+sum[pri[0]-1];
        }
        fo(j,1,pri[0])
        {
            if (pri[j]*i>n) break;
            t=pri[j]*i;
            pd[t]=1;
            if (i%pri[j]==0)
                break;
        }
    }
}
ll calc(ll x,ll y)
{
    tt=0;
    le=1;
    while (le<=x)
    {
        v=x/le;
        ri=x/v;
        df[++tt]=1ll*v*(v+1)/2-1;
        if (v<sn) f[0][v]=tt;
        else f[1][le]=tt;
        val[tt]=v;
        le=ri+1;
    }
    siz=tt;
    fo(j,1,y)
    {
        ri=1;
        while (!(val[tt]/pri[j]/pri[j])) tt--;
        fo(i,1,tt)
        {
            v=val[i]/pri[j];
            if (v<sn) p=f[0][v];else p=f[1][n/v];
            tmp=df[p]-sum[j-1];
            if (val[i]==n) 
                ret+=tmp;
            df[i]=df[i]-1ll*tmp*pri[j];
        }
    }
    return df[1];
}
ll calc(ll n)
{
    int j;
    ret=0;
    sn=trunc(sqrt(n));
    fo(j,1,pri[0]) if (pri[j]>sn) break;
    j--;
    calc(n,j);
    return ret;
}
int main()
{
    freopen("t1.in","r",stdin);
    //freopen("factor.out","w",stdout);
    predo(2e5);
    scanf("%lld %lld\n",&l,&r);
    printf("%lld\n",calc(n=r)-calc(n=(l-1)));
}
在给定区间内计算每个数的最大因数的个数,并优化时间复杂度,可以通过以下方法实现: ### 1. 预处理最小质因数 首先,可以利用线性法(埃拉托斯特尼法的优化版本)预处理出每个数的最小质因数。这种方法的时间复杂度为 $O(n)$,适用于较大范围的区间。在预处理完成后,可以快速分解每个数的质因数,并提取最大因数。 以下是一个实现最小质因数预处理的代码示例: ```cpp #include <iostream> #include <vector> using namespace std; const int N = 1000010; // 最大范围 int min_prime[N]; // 存储每个数的最小质因数 vector<int> primes; // 存储质数 void preprocess() { for (int i = 2; i < N; ++i) { if (min_prime[i] == 0) { // 如果i是质数 min_prime[i] = i; // 自己是最小质因数 primes.push_back(i); // 加入质数列表 } for (int j = 0; j < primes.size() && primes[j] <= min_prime[i] && i * primes[j] < N; ++j) { min_prime[i * primes[j]] = primes[j]; // 标记i * primes[j]的最小质因数 } } } ``` ### 2. 分解并提取最大因数 通过预处理得到的最小质因数数组,可以高效地分解每个数的质因数。例如,对于一个数 $x$,可以通过不断除以它的最小质因数,直到 $x = 1$ 为止,从而得到所有质因数。在这个过程中,记录最大的质因数即可。 以下是一个分解每个数并计算最大因数的代码示例: ```cpp int max_prime_factor(int x) { int max_factor = 0; while (x > 1) { int factor = min_prime[x]; // 获取最小质因数 max_factor = max(max_factor, factor); // 更新最大因数 while (x % factor == 0) { x /= factor; // 除以该质因数 } } return max_factor; } ``` ### 3. 统计区间内每个数的最大因数个数 在预处理和分解的基础上,可以遍历给定区间内的每个数,计算其最大因数,并统计其出现次数。例如,对于区间 $[l, r]$,可以使用一个哈希表或数组来记录每个最大因数的出现次数。 以下是一个完整的统计代码示例: ```cpp #include <iostream> #include <vector> #include <unordered_map> using namespace std; const int N = 1000010; // 最大范围 int min_prime[N]; // 存储每个数的最小质因数 unordered_map<int, int> count_map; // 记录最大因数的出现次数 // 预处理最小质因数 void preprocess() { for (int i = 2; i < N; ++i) { if (min_prime[i] == 0) { // 如果i是质数 min_prime[i] = i; // 自己是最小质因数 for (long long j = (long long)i * i; j < N; j += i) { if (min_prime[j] == 0) { min_prime[j] = i; } } } } } // 计算x的最大因数 int max_prime_factor(int x) { int max_factor = 0; while (x > 1) { int factor = min_prime[x]; // 获取最小质因数 max_factor = max(max_factor, factor); // 更新最大因数 while (x % factor == 0) { x /= factor; // 除以该质因数 } } return max_factor; } // 统计区间[l, r]内每个数的最大因数的出现次数 void count_max_prime_factors(int l, int r) { for (int i = l; i <= r; ++i) { int max_factor = max_prime_factor(i); count_map[max_factor]++; } } int main() { preprocess(); // 预处理最小质因数 int l, r; cin >> l >> r; count_max_prime_factors(l, r); // 统计最大因数 for (auto &entry : count_map) { cout << "最大因数 " << entry.first << " 出现了 " << entry.second << " 次" << endl; } return 0; } ``` ### 4. 时间复杂度分析 - **预处理最小质因数**:时间复杂度为 $O(n)$,其中 $n$ 是最大范围。 - **分解每个数的最大因数**:对于每个数 $x$,分解的时间复杂度与质因数的数量相关,平均情况下为 $O(\log x)$。 - **统计区间内每个数的最大因数**:时间复杂度为 $O(r - l + 1) \cdot O(\log x)$,其中 $r - l + 1$ 是区间长度。 综上,整个算法的时间复杂度接近 $O(n + (r - l + 1) \cdot \log x)$,适用于较大范围的区间。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值