HDU 2017 多校联合训练赛4 4003 6069 Counting Divisors 素数筛和素因数分解

Counting Divisors

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)


Problem Description
In mathematics, the function d(n) denotes the number of divisors of positive integer n .

For example, d(12)=6 because 1,2,3,4,6,12 are all 12 's divisors.

In this problem, given l,r and k , your task is to calculate the following thing :

(i=lrd(ik))mod998244353

 

Input
The first line of the input contains an integer T(1T15) , denoting the number of test cases.

In each test case, there are 3 integers l,r,k(1lr1012,rl106,1k107) .
 

Output
For each test case, print a single line containing an integer, denoting the answer.
 

Sample Input
  
  
3 1 5 1 1 10 2 1 100 3
 

Sample Output
  
  
10 48 2302
 

Source
2017 Multi-University Training Contest - Team 4


题目大意
已知l,r,k,i为区间 [ l , r ] 中的任意整数,求所有 i^k 的因子个数的和。


题目分析
首先我们要知道两条性质:
1.对于 每个大于1的 自然数, 都可以分解为质数的 积的形式 ,并且这种形式是唯一的 。举个栗子:6936 = 2^3*3*17^2。

2.这个数n的约数的个数是:(接上一个栗子:f(6936) = (3+1)*(1+1)*(2+1) = 24)

其中,p1、p2、p3,…pk为互异质数,其中a1、a2、a3…ak是p1、p2、p3,…pk的指数。

另外,对于一个数i^k,它的约数个数是(a1*k+1)(a2*k+1)……(ai*k+1)。

这样,问题就转化成,对一个数i的质因数分解,一开始的思路是首先筛出小于10^6所有素数,然后进行质因数分解,结果超时了。赛后看题解,标程的做法是,设置了两个数组f[ ](存区间[ l , r ]的所有数),g[ ](存对应f[ ]的约数个数的计算结果),枚举小于10^6的所有质数,针对枚举的质数p,分解所有p的倍数的以p作为质因子的项,(注意,数组f[ ]和g[ ]存的都是分解到当前质因子p的结果,详情请参考代码函数work( ))。
标程的方法快在,只需要遍历一边质数集合就将区间中的所有数都分解好了,而不是区间中的每个数都分别进行因数分解。

最后,需要提醒一下,对于一个数i的质因数分解,最终剩下的部分可能是1或者是一个大于sqrt(i)的质数,所以,在最后,如果f[i]>1,我们需要将这个质因子加到最终结果中。


另附官方题解

n=p1c1p2c2...pmcm,则d(nk)=(kc1+1)(kc2+1)...(kcm+1)

枚举不超过r\sqrt{r}的所有质数pp,再枚举区间[l,r][l,r]中所有pp的倍数,将其分解质因数,最后剩下的部分就是超过r\sqrt{r}的质数,只可能是00个或1个。

时间复杂度O(r+(r−l+1)loglog(r−l+1))O(\sqrt{r}+(r-l+1)\log\log(r-l+1))



代码
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 1000010;
const int mod = 998244353;
int cas, k;
ll l, r, n;//n为区间长度
ll f[N];//记录区间l~r之间的所有数,并且用于素数分解操作
int g[N];//记录f[]中所对应的数的分解素数的(指数*k+1)的累乘积
int tot;//总质数的个数
int prime[N/10];//记录所有的质数
bool vis[N];//1为合数,0为质数
int i, j;
ll ans;
int cnt;

void isprime()
{
    tot = 0;
    //memset(vis, 0, sizeof(vis));
    for (i=2; i<N; i++)
    {
        if (!vis[i])
            prime[tot++] = i;
        for (j=0; j<tot&&i*prime[j]<N; j++)
        {
            vis[i*prime[j]] = 1;
            if (i%prime[j] == 0)
                break;
        }
    }
}

inline void work(ll p)
{
    for (ll i=l/p*p; i<=r; i+=p)//i=p-(l%p),求p的第一个倍数的位置
    {
        if(i>=l)
        {
            cnt = 0;//p的指数
            while (f[i-l] && f[i-l]%p == 0)
            {
                f[i-l] /= p;
                cnt ++;
            }
            g[i-l] = 1LL*g[i-l]*(cnt*k+1)%mod;
        }
    }
}

int main()
{
    isprime();
    //for (i=0; i<tot; ++i)
        //printf ("%d\n",prime[i]);
    scanf ("%d",&cas);
    while (cas--)
    {
        scanf("%lld %lld %d",&l,&r,&k);
        n = r-l;
        for (i=0; i<=n; ++i)
        {
            f[i] = i+l;
            g[i] = 1;
        }
        for (i=0; i<tot; ++i)
        {
            if (1LL*prime[i]*prime[i] > r)
                break;
            work(prime[i]);

        }
        ans = 0;
        for (i=0; i<=n; i++)
        {
            if(f[i] > 1)
                g[i] = 1LL*g[i]*(1*k+1)%mod;
            ans = (ans+g[i])%mod;
        }
        printf ("%lld\n",ans);
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值