Counting Divisors
Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1451 Accepted Submission(s): 528
Total Submission(s): 1451 Accepted Submission(s): 528
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 :
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(1≤T≤15)
, denoting the number of test cases.
In each test case, there are 3 integers l,r,k(1≤l≤r≤1012,r−l≤106,1≤k≤107) .
In each test case, there are 3 integers l,r,k(1≤l≤r≤1012,r−l≤106,1≤k≤107) .
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
题目大意:求在区间l~r 的数i求i^k因子的个数相加的和。
解题思路:根据约数个数定理
那么对于每一个i对于它的i^k来说它的约数个数就为(a1*k+1)*(a2*k+1)……(an*k+1)那么就能想到我们可以遍历每一个数对它进行质因数分解然后根据指数算出这个数的因子个数,但是进过很多次的尝试之后发现时间复杂度太高了,原因在于对于每一个数我们都需要遍历求质因子的指数。然后我们可以想到怎么能进行优化呢?但是比赛的时候怎么都没想起来。事后看了题解:
这个思路快就快在它是以质数为出发点的,通过算出每一个质数在区间内的每一个数以它为质因子的指数的大小,进一步算出因子的个数。(f与g数组很关键)。而原来的思路是求每一个数的所有质因子的指数求因子的个数。灵活啊,有点区间筛的意思。
解题思路:根据约数个数定理
那么对于每一个i对于它的i^k来说它的约数个数就为(a1*k+1)*(a2*k+1)……(an*k+1)那么就能想到我们可以遍历每一个数对它进行质因数分解然后根据指数算出这个数的因子个数,但是进过很多次的尝试之后发现时间复杂度太高了,原因在于对于每一个数我们都需要遍历求质因子的指数。然后我们可以想到怎么能进行优化呢?但是比赛的时候怎么都没想起来。事后看了题解:
设n=p1c1p2c2...pmcmn=p_1^{c_1}p_2^{c_2}...p_m^{c_m}n=p1c1p2c2...pmcm,则d(nk)=(kc1+1)(kc2+1)...(kcm+1)d(n^k)=(kc_1+1)(kc_2+1)...(kc_m+1)d(nk)=(kc1+1)(kc2+1)...(kcm+1)。
枚举不超过sqrtr的所有质数p,再枚举区间[l,r]中所有p的倍数,将其分解质因数,最后剩下的部分就是超过sqrt{r}的质数,只可能是0个或1个。
时间复杂度O(sqrtr+(r−l+1)loglog(r−l+1))l显然题解写的很混乱基本看不出什么(对于我们这些菜鸡来说。。。。。大佬都是一眼看穿。。。。)。
解析:我们可以先筛出1000000的素数然后对于每一个区间l~r来讲找出小于根号下r的所有质数(因为大于根号下r的质数最多一次这些数在l~r区间里都存在了方而平方后就超过了r)然后我们遍历每一个质数(小于根号r)然后算出每一个质数在l~r每一个 数分解该质数的得到其指数大小并根据约数个数定理计算因子的个数。在这里我们设一个数组f以及一个数组g。分别储存每一个l~r的数和这个数因子的个数。
void work(ll p)///对于每一个小于根号下r的素数而言计算找到l~r区间内所有的它的倍数并记下它的倍数以这个质数分解质因子的指数个数并根据约数个数定理求因子的个数
{
for(ll i=l/p*p; i<=r; i+=p)
{
if(i>=l)
{
int o=0;
while(f[i-l]%p==0)
{
f[i-l]/=p;
o++;
}
g[i-l]=1LL*g[i-l]*(o*k+1)%P;///根据约数个数定理计算l~r内每一个数的因子个数并以数组g的形式存起来
}
}
}
在对于for循环中
for(ll i=l/p*p; i<=r; i+=p)
为什么要令
i=l/p*p
举个栗子p=3,l=100,100/3=99,99<100,99+3=102>l=100,那么102-100=2,而f数组中的f[2]=102,102%3==0,这就是巧妙所在。
这样就把所有小于根号下r的质数对于每一个数的质因子为它的指数求出,并能以此求的因子的个数。
AC代码:(根据标程改编)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
typedef long long ll;
const int N=1000010,P=998244353;
int Case,i,j,k,p[N/10],tot,g[N],ans;
ll n,l,r,f[N];
bool v[N];
void work(ll p)///对于每一个小于根号下r的素数而言计算找到l~r区间内所有的它的倍数并记下它的倍数以这个质数分解质因子的指数个数并根据约数个数定理求因子的个数
{
for(ll i=l/p*p; i<=r; i+=p)
{
if(i>=l)
{
int o=0;
while(f[i-l]%p==0)
{
f[i-l]/=p;
o++;
}
g[i-l]=1LL*g[i-l]*(o*k+1)%P;///根据约数个数定理计算l~r内每一个数的因子个数并以数组g的形式存起来
}
}
}
int main()
{
for(i=2; i<N; i++)///素数筛法
{
if(!v[i])p[tot++]=i;
for(j=0; j<tot&&i*p[j]<N; j++)
{
v[i*p[j]]=1;
if(i%p[j]==0) break;
}
}
scanf("%d",&Case);
while(Case--)
{
scanf("%lld%lld%lld",&l,&r,&k);
n=r-l;
for(i=0; i<=n; i++)
{
f[i]=i+l;///f数组存的是l~r这些数数组的下标从0开始的,而g数组存的是对应每一个f数组里的数所有质数指数的和
g[i]=1;
}
for(i=0; i<tot; i++)///对小于根号下r的素数进行遍历
{
if(1LL*p[i]*p[i]>r)
{
break;
}
work(p[i]);
}
ans=0;
for(i=0; i<=n; i++)
{
if(f[i]>1)
{
g[i]=1LL*g[i]*(k+1)%P;///此时的f数组内存的要不是1要不就大于1比如像8这样的数我们在找质数2的时候就把储存它的f数组除干净了这是的f数组就为1
}
ans=(ans+g[i])%P;///把所有因子的个数相加取模
}
printf("%d\n",ans);
}
return 0;
}
这个思路快就快在它是以质数为出发点的,通过算出每一个质数在区间内的每一个数以它为质因子的指数的大小,进一步算出因子的个数。(f与g数组很关键)。而原来的思路是求每一个数的所有质因子的指数求因子的个数。灵活啊,有点区间筛的意思。