Problem Statement |
|||||||||||||
|
For an integer n, let F(n) = (n - 0^2) * (n - 1^2) * (n - 2^2) * (n - 3^2) * ... * (n - k^2), where k is the largest integer such that n - k^2 > 0. You are given three long longslo, hi and divisor. It is guaranteed thatdivisor will be a prime number. Compute and return the number of integers n betweenlo and hi, inclusive, such that F(n) is divisible bydivisor. |
||||||||||||
Definition |
|||||||||||||
|
|
||||||||||||
|
|||||||||||||
|
|||||||||||||
Constraints |
|||||||||||||
- |
lo will be between 1 and 1,000,000,000,000, inclusive. |
||||||||||||
- |
hi will be between lo and 1,000,000,000,000, inclusive. |
||||||||||||
- |
divisor will be between 2 and 997, inclusive. |
||||||||||||
- |
divisor will be a prime number. |
||||||||||||
Examples |
|||||||||||||
0) |
|
||||||||||||
|
|
||||||||||||
1) |
|
||||||||||||
|
|
||||||||||||
2) |
|
||||||||||||
|
|
||||||||||||
3) |
|
||||||||||||
|
|
||||||||||||
4) |
|
||||||||||||
|
|
||||||||||||
5) |
|
||||||||||||
|
|
类型:数论 难度:2.5
题意:给出lo,hi,div三个数,定义函数
F(n) = (n - 0^2) * (n - 1^2) * (n - 2^2) * (n - 3^2) * ... * (n - k^2) |
k是满足n-k^2>0的最大整数。
求在[lo,hi]区间内满足F(n)能被div整除的数的个数。
分析:直接遍历[lo,hi]的所有数并判定F(n)能否被div整除不可行,应为hi-lo的数量级在10^12。
这里想到计算素数的思想(从小到大找素数,将素数的整数倍标记为非素数),考虑若F(n)能被div整除,那么F(n)的因式 (n-k^2),0<=k<√n 必定至少有一个能被div整除,也就是说 n-y^2 = div*x,所以考虑 div*x+y^2,x>=1,y>=0 这个式子的值,如果这个式子的值落在[lo,hi]区间,那么这个数就是满足要求的。那么,遍历x,y,将落在区间内的值加入一个结果set集保证不重,最后统计set的大小就是结果。但是复杂度仍高,考虑当div=2时,复杂度为10^12 * 10^6,显然不能接受。一定把复杂度降低到10^6。
考虑简化上一种方法,由于外层循环是遍历x,寻找落在区间内的 div*x +y^2,那么若能知道每个 [div*x,div*(x+1)) 区间内的合法的n的个数,那么再乘以x落在区间内的可能值,就能在O(1)算出结果。运用这个思路,再进一步细化,并注意边界条件。
设rc[i]表示 y^2 % div = i 时,y^2 / div 的值再加1,表示这个余数最开始在div的第几个倍数中出现。遍历所有的y,1<=y<√hi,得到rc数组的值。
再计算rc数组中出现的余数的数目cnt,即每div个数有多少个满足条件。再计算所有rc数组的最大值st,即从div的第几个倍数开始,后续的每div个数都一定有cnt个满足要求的数。
之后就是考虑lo,hi两个数的边界条件,记录[lo,div*left)区间和[div*right,hi]区间内满足要求的数的个数,left取满足div*left >=lo的最小值,right取满足div*right<=hi的最大值
再然后,考虑此时的left和st的关系,若left<st,则此时每div个数符合要求的数还不到cnt个,需遍历rc数组,递增left,直到left=st。
之后就可以计算[left,right]区间内的符合要求的个数,即cnt*(right-left)。
代码如下:
#include<string> #include<cstdio> #include<vector> #include<cstring> #include<map> #include<iostream> #include<algorithm> #include<cmath> #include<set> using namespace std; class SparseFactorialDiv2 { public: long long getCount(long long lo, long long hi, long long divisor) { long long rc[1000]; memset(rc,-1,sizeof(rc)); rc[0] = 1; for(long long i=1; i*i<hi; i++) { long long a = (i*i)/divisor; long long b = (i*i)%divisor; if(rc[b]<0) rc[b] = a+1; } long long st = -1, cnt = 0; for(long long i=0; i<divisor; i++) { if(rc[i]>-1) cnt++; if(rc[i]>st) st = rc[i]; } long long lefta = lo/divisor; long long leftb = lo%divisor; long long righta = hi/divisor; long long rightb = hi%divisor; long long ans = 0; if(lefta==righta) { for(long long i=leftb; i<=rightb; i++) if(rc[i]>0 && lefta >= rc[i]) ans++; } else { for(long long i=leftb; i<divisor; i++) if(rc[i]>0 && lefta >= rc[i]) ans++; lefta++; for(; lefta < st && lefta < righta; lefta++) { for(long long i=0; i<divisor; i++) if(rc[i]>0 && lefta >= rc[i]) ans++; } for(long long i=0; i<=rightb; i++) if(rc[i]>0 && righta >= rc[i]) ans++; ans += cnt*(righta-lefta); } return ans; } }; int main() { }