“丑陋数字是唯一素数为2、3或5的数字。 顺序1、2、3、4、5、6、8、9、10、12、15…显示了第一个丑陋的11 数字。按照惯例,其中包括1。“
给出数字X,确定该序列中X的顺序。 示例:X = 12,输出:10。
我制作了一种蛮力算法,可以在O(XlogX)中运行:
long long cnt = 0;
for(long long i = 1; i<X; i++)
{
long long tmp = i;
while(tmp % 2 == 0) tmp/=2;
while(tmp % 3 == 0) tmp/=3;
while(tmp % 5 == 0) tmp/=5;
if(tmp == 1) cnt ++;
}
cout << cnt+1 << endl;
但是,X可能是1e18,可能有10 ^ 5个查询,每个查询给我们一个数字X。
有人知道执行此操作的更有效算法吗?谢谢。
答案 0 :(得分:0)
单次查询
您可以使用以下算法,通过计算n
下面的丑陋数字的数量来获得X
的位置X
:
int get_position(long long X)
{
int n = 0;
for(long long n2=1; n2<=X; n2*=2)
for(long long n3=n2; n3<=X; n3*=3)
for(long long n5=n3; n5<=X; n5*=5)
++n;
return n;
}
该算法循环遍历2、3和5的所有倍数,并以O(n)
(其中n~log(X)³
)运行。
多个查询
如果要重复多次操作,可以保留一个表以执行二进制搜索:
struct ugly_numbers
{
std::vector<long long> numbers{1};
int get_position(long long X)
{
if(X>numbers.back())
{
std::set<long long> number_set;
for(long long n2=1; n2<=X; n2*=2)
for(long long n3=n2; n3<=X; n3*=3)
for(long long n5=n3; n5<=X; n5*=5)
number_set.insert(n5);
numbers.assign(number_set.begin(), number_set.end());
}
auto value_it = std::upper_bound(numbers.begin(),numbers.end(),X);
return (int)std::distance(numbers.begin(),value_it);
}
};
当数字是高速缓存的一部分时,此算法在O(log(n))
中运行,而当需要重新创建高速缓存时,该算法在O(n*log(n))
中运行。另外,您可以使用最大期望数量预先创建缓存,以分摊缓存创建的成本。
避免溢出
对于数量接近类型long long
的最大值的数字查询,可能会发生溢出错误和无限循环。为避免这种情况,请使用以下代码(并对摊销版本使用类似的逻辑):
int get_position(long long X)
{
int n=0;
long long max_n2 = std::min(X,std::numeric_limits<long long>::max() / 2);
long long max_n3 = std::min(X,std::numeric_limits<long long>::max() / 3);
long long max_n5 = std::min(X,std::numeric_limits<long long>::max() / 5);
for(long long n2=1; n2<=max_n2; n2*=2)
for(long long n3=n2; n3<=max_n3; n3*=3)
for(long long n5=n3; n5<=max_n5; n5*=5)
++n;
return n;
}