POJ3604 Professor Ben[线性筛/暴力]

Professor Ben

  POJ - 3604





 

题意:

给 T 组数据,每组数据含有一个 n,让你求出(d(x)是指 x 的因子个数)


题解:

一看到题目,我们会比较倾向于这样的打表。

void initial()
{
    sum[1]=1;
    for (int i=2 ; i<N ; ++i)
    {
        for (int j=i ; j<N ; j+=i)
        {
            d[j]++;
            sum[j]+=(d[j]+1)*(d[j]+1)*(d[j]+1);
        }
    }
}

然而返回结果,却是一直的TLE,那么显然这样的打表,是行不通的。

我们要更换一下思路,既然这样打表行不通,那么O(n)的线性筛呢?

我们先单独看 只有素因子的数的因子数情况。

例如 4  那么答案是    其中 1 是因子 1 的因子个数,2 是因子 2 的因子个数,3 是因子 4 的因子个数。


可以很容易发现,如果单独是一个素因子构成的数,分解成只有素因子组成的形式

那么它的结果应该是

再扩展到含有其他素因子组成的数,即这种。


关键就在于,这样子是否成立。我们暂时忽略掉三次方。


实际上,这式子就是算出 当前被分解的这个数 N 的所有因子数的因子个数的和。

其实这式子,更像是单独求 N 的约数个数这样是把全部因子的权值变为1。

而题目要求的是要知道全部因子数的因子个数,所以给他们附上一定的权值 (自己觉得这里讲的有点牵强,可能需要自己理解一下)

而根据组合来说,就可以很容易说明这式子的合法性。[实在觉得没办法理解合法性,自己手动写几个例如 12 啊 等等之类的合数,然后数学归纳法归纳一下。]

这个式子是成立的话,那么我们就可以用线性筛来打表了,因为我们 每次只需要知道最小素因子的个数,就可以打表了,跟约数个数是一个原理。证明我之前有一篇写线性筛的博客证明过,就不在这里证明了,有兴趣的可以自己找来看看 (OTL本渣渣写的还可能有错误呢,如果发现错误,麻烦帮忙指出来)  线性筛 [约数个数/约数和]


接下来还要处理三次方的问题。

先推荐这篇博客。有一个式子可以求出幂和的,即求

http://blog.miskcoo.com/2014/09/power-sum


有了这个式子,我们可以直接推导出 就是等于 


不想直接打表得到结果的,完全可以先筛全部素数,然后暴力分解 N,然后再用上面的式子得到答案。


====================================================================

这些小细节都知道怎么处理了,我们就可以开始打表了。


下面的各种情况,其实跟打约数个数跟约数和的情况是一直的,了解的话,完全可以自己知道式子就能打出来。因为之前写过一篇线性筛的博客,就懒得写的那么详细了。

sum[i]就是 i 的结果

num[i]就是 i 的最小素因子的个数


① n 是素数

那么结果显然是就是 (1+8) 就是说只有自己本身跟 1 这两个因子数,而本身的因子个数为 2。

记录下最小素因子个数。

sum[i]=1+8;

num[i]=1;


② i % prim[j] != 0

因为前面  i 中不存在 prim[j] 这个素因子,但是 i*prim[j] 中存在 prim[j] 这个素因子。

因此结果应该是 sum[i*prim[j]] = sum[i]*sum[prim[j]]

记录下当前最小素因子个数 num[i*prim[j]] = 1 

(因为每次扫的必然是最小素因子,至于为什么,本身线性筛就是被最小素因子筛出来的)


③ i % prim[j] == 0

而这种情况,i 中包含了 prim[j] 这个素因子,意味着在最小素因子的那一项,会多了一项

即成了

原本的 i 应该是

那么为了得到 i*prim[j] 的结果

我们需要 将 sum[i] /  * 

那么就需要用到我们之前记录下的最小素因子个数,来得到这个结果。

最后结果 sum[i*prim[j]] = sum[i] / s3(num[i]+1) * s3(num[i]+2)  [s3 是用来求的函数]

继续记录下最小素因子个数 num[i*prim[j]] = num[i] + 1


打表结束,我们就可以得到我们需要的答案了。

要说明一下,这个题的内存卡的有点严,如果再开一个数组就会 MLE 所以我没有直接再开一个数组来保存最小素因子那一项的和,而是直接用公式求出。


[OTL 再一次知道线性筛的厉害啊]


打表

#pragma comment(linker, "/STACK:102400000,102400000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<vector>
using namespace std;
typedef long long ll;
const int N=5e6+5;
int prim[N],num[N],sum[N];
bool mark[N];
int cnt;
int s3(int x)
{
    return (x+1)*(x+1)*(x+2)*(x+2)/4;
}
void initial()
{
    cnt=0;
    sum[1]=1;
    for (int i=2 ; i<N ; ++i)
    {
        if (!mark[i])
        {
            prim[cnt++]=i;
            num[i]=1;
            sum[i]=9;
        }
        for (int j=0 ; j<cnt && i*prim[j]<N ; ++j)
        {
            mark[i*prim[j]]=1;
            if (!(i%prim[j]))
            {
                num[i*prim[j]]=num[i]+1;
                sum[i*prim[j]]=sum[i]/s3(num[i])*s3(num[i*prim[j]]);
                break;
            }
            sum[i*prim[j]]=sum[i]*sum[prim[j]];
            num[i*prim[j]]=1;
        }
    }
}
int main()
{
    initial();
    int T;
    scanf("%d",&T);
    while (T--)
    {
        int n;
        scanf("%d",&n);
        printf("%d\n",sum[n]);
    }
    return 0;
}

暴力分解

#pragma comment(linker, "/STACK:102400000,102400000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e4+5;
bool mark[N];
int prim[N];
int num[N];
int cnt;
void initial()
{
    cnt=0;
    for (int i=2 ; i<N ; ++i)
    {
        if (!mark[i])
            prim[cnt++]=i;
        for (int j=0 ; j<cnt && i*prim[j]<N ; ++j)
        {
            mark[i*prim[j]]=1;
            if (!(i%prim[j]))
                break;
        }
    }
}
ll divi(int n)
{
    int k=0;
    ll ans=1;
    for (int i=0 ; i<cnt ; ++i)
    {
        if (prim[i]*prim[i]>n)
            break;
        if (!(n%prim[i]))
        {
            int ch=0;
            while (!(n%prim[i]))
            {
                ch++;
                n/=prim[i];
            }
            ans*=(ch+1)*(ch+1)*(ch+2)*(ch+2)/4;
        }
    }
    if (n>1)
        ans*=(1+8);
    return ans;
}
int main()
{
    initial();
    int T;
    scanf("%d",&T);
    while (T--)
    {
        int n;
        scanf("%d",&n);
        printf("%lld\n",divi(n));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值