关于队里面最菜的在博客打卡第二十八天这件事

今天打了场赛氪,有一道题记忆犹新,题是让你找规律,实际上是让你找 1 到 n 的所有 数的平方的约数和,n的范围有1e7, 当时想的十分简单,直接用  用欧拉晒去求质因数(当时鬼使神差的认为复杂度是 log n,然后再求的过程中记录下来约数的个数并且累加起来 , 时间复杂度只有 sqrt(n) * n 的样子, 然而题目的限制只有1000ms,笑死,根本过不去好吧  . (代码就不写了,当时代码写的和一坨那啥一样)

蓝后,就只能另辟蹊径了,开始思路是 既然每一个数都可以化 为 一系列质数的乘积,并且两个相互为倍数的a和b , 可以让a去乘以一系列质数然后转化为b,并且特定数量的质数的乘积对应的数是唯一的。也就是两个数,将他们的质因数全部求出,只要有一个数不同或者同一个质因数的幂数不同,那么a和b就一定不想等,如果这是一个搜索的过程就表明这个搜索不需要回溯,所以根据这个性质,想到了找出1到n的所有质数,然后从1开始bfs,每次记录遍历到的数和当前数的约数, 并且横向搜幂次, 纵向搜质数(每个质数都是不同的),并且经过合理的剪肢,可以几乎优化到 O(n)的时间去求区间所有数的约数的个数,并且几乎可以保证每个数遍历不超过两次(当时想到这个方法的时候整个人都开心的飞起)然后我们就根据这个性质去bfs暴搜每一个数。

这是当时的代码:

#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<iostream>
const int maxn=1e7+7;//总的范围规定在这里
using namespace std;
typedef long long int LL ;
const int N = 1e7+7;
const LL mod = 998244353;
//我们将这个埃氏筛法写成一个函数
bool isprime[maxn];//全局变量
void sieve(){
    for(int i=0;i<=maxn;i++)isprime[i]=true;

    isprime[0]=isprime[1]=false;
    for(int i=2;i<=maxn;i++){//从2开始往后筛
        if(isprime[i]){
            for(int j=2*i;j<=maxn;j+=i){
                isprime[j]=false;
            }
        }
    }
}
LL n;
LL su[5000000 + 10];
LL ans = 0;
LL cnt = 0 ;
LL etf = 0 ;
LL now = 1 ;
void dfs(int step , LL now ,LL res,int mi,bool f )
{
    etf++ ;
   //cout <<su[step] <<' '<< step <<' '<< now <<' ' <<res <<' '<< mi <<' '<<now * su[step]<<endl;
    if(step >= cnt)return;
    if(f)
    ans = (ans + (res * (mi + 1)%mod))%mod;
    if(now  *  su[step] <= n)
    {
        dfs(step,now * su[step],res,mi + 2,1);
    }
    if(step < cnt-1 && now * su[step + 1] <= n)dfs(step+1,now,res*(mi + 1)%mod,0,0);
}
int main()
{
    sieve();//预处理
    cin >> n ;  
    for(int i = 2; i <= n ; i ++)
    {
        if(isprime[i])
        {
            su[cnt++] = i;
        }
    }
    dfs( 0, 1,1,0, 1);
    cout << etf << endl;
    cout << ans <<endl;
}

最后的ans就是1到n内所有约数的个数。etf表示的是所有dfs过程一共搜索了几次。。我们可以用一个大一些的数据来看出来剪枝的有效性

 我们可以看到一个1e5的数据,他的暴搜次数没有超过他的两倍。但是我们测了一个神奇的数据,也就是1到1e7的范围内有多少个质数:664579 , 这个深度的纵向和横向如果同时搜的话,绝对会爆栈。。。于是,我们测试了一下,结果发现数据到4e5左右时,程序就会异常终止(也就是爆栈了)。然后没办法了,只能优化了

然后我们发现了一个更神奇的东西,一个数再遍历到之后,并且求出他对应的约数,然后再用该数去更新他的倍数之后,这个数在以后的操作中都不会被用到,所以我们就想到了如果模拟这个dfs的过程,并且在过程中将遍历到的数并且更新完之后的数后,将这个点直接抛弃掉,并且全程都在堆空间上操作,这个过程可以模拟一个类似bfs的过程,模拟一个栈,并且暴搜(赛后思考发现这可以归纳为一个bfs的过程),这样可以防止栈区的空间溢出。以下就是代码

#include <stdio.h>
#include <algorithm>
#include <math.h>
#include <iostream>
const int maxn = 1e7 + 7; //总的范围规定在这里
using namespace std;
typedef long long int LL;
const int N = 1e7 + 7;
const LL mod = 998244353;
//我们将这个埃氏筛法写成一个函数
bool isprime[maxn]; //全局变量
void sieve()
{
    for (int i = 0; i <= maxn; i++)
        isprime[i] = true;

    isprime[0] = isprime[1] = false;
    for (int i = 2; i <= maxn; i++)
    { //从2开始往后筛
        if (isprime[i])
        {
            for (int j = 2 * i; j <= maxn; j += i)
            {
                isprime[j] = false;
            }
        }
    }
}
LL n;
LL su[10000000];
LL ans = 0;
LL cnt = 0;
LL etf = 0;
LL now = 1;
struct node
{
    LL step, now, res, mi;
    bool f;
} p[N];
int top;
void dfs(int step, LL now, LL res, int mi, bool f)
{
    etf++;
    p[++top] = {step, now, res, mi, f};
    while (top > 0)
    {
        auto t = p[top --];
        step = t.step;
        now  = t.now ;
        res  = t.res;
        mi   = t.mi;
        f    = t.f;
        if (f)
            ans = (ans + (res * (mi + 1) % mod)) % mod;

        if (now * su[step] <= n)
        {
            p[++top] = {step, now * su[step], res, mi + 2, 1};
        }
        if (step < cnt - 1 && now * su[step + 1] <= n)
            p[++top] = {step + 1, now, res * (mi + 1) % mod, 0, 0};
    }
}
int main()
{
    sieve(); //预处理
    cin >> n;
    if(n == 1)
    {
        cout << 1 <<endl;
        return 0 ;
    }
    for (int i = 2; i <= n; i++)
    {
        if (isprime[i])
        {
            su[cnt++] = i;
        }
    }
    dfs(0, 1, 1, 0, 1);
    cout << ans << endl;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值