PC/UVa:110607/10049
看前边很多题都需要高精度运算就没做,就捡了一个软的柿子捏,结果这个也不软。
这个序列还是很好递推的,从3
开始最好推,也就是build()
函数,但是循环还不到最大上上限的一半就已经要用很长时间了,所以肯定不能这么做。
通过观察这个自描述序列,可以发现映射后的值小于映射之前的值,所以可以存储逆映射,而且通过uDebug上的测试,最大值2 000 000 000
的函数值也只有673365
,这么多项的数组内存还是不会超的。
build2()
函数负责构建viFirst
这个数组,表示数字num
第一次出现的位置,前3
项分别初始化为0
(无意义)、1
(1
这个数第一次出现的位置为1
)、2
(2
这个数第一次出现的位置为2
),接下来while
中第一次加入的数是4
,表示3
第一次出现的位置为4
,然后sum
增加序列中3
的数量。
更重要的一点是如何在viFirst
中找到3
的个数,这可以通过在viFirst
中查找第一个大于等于3
的索引i
来计算。如果viFirst
中包含3
,相应索引为i
,表示自描述序列中位置3
的值为i
,也就是3
的个数;如果viFirst
中不包含3
,但是第一个大于3
的值的索引为i
,则有viFirst[i - 1] < 3 < viFirst[i]
,表示数字i - 1
第一次出现的位置小于3
,而数字i
第一次出现的位置大于3
,根据序列的性质,位置3
的值为i - 1
。
如果用O(n)的查找算法来查找个数,又会超时了,所以就又写了二分查找(这又调了好久 ???),这样时间复杂度就从O(n^2)下降到O(nlogn)了。
#include <iostream>
#include <vector>
using namespace std;
void build(vector<int> &viF)
{
viF.push_back(1);
viF.push_back(2);
viF.push_back(2);
for (size_t i = 3; i < 1000000000; i++)
{
for (int j = 0; j < viF[i - 1]; j++)
{
viF.push_back(i);
}
}
}
int find2(vector<int> &viFirst, int ele)
{
size_t begin = 0, end = viFirst.size(), mid;
int ret = -1;
while (end - begin > 1){
mid = (begin + end) >> 1;
if (viFirst[mid] < ele) begin = mid;
else if (viFirst[mid] > ele) end = mid;
else{
ret = mid;
break;
}
}
if (ret == -1){
if (viFirst[end] == ele) ret = end;
else ret = begin;
}
return ret;
}
void build2(vector<int> &viFirst)
{
viFirst.push_back(0);
viFirst.push_back(1);
viFirst.push_back(2);
int sum = 4;
int num = 3;
while (1){
viFirst.push_back(sum);
if (sum >= 2000000000) break;
/*
for (size_t i = 0; i < viFirst.size(); i++)
{
if (num == viFirst[i]){
sum += i;
break;
}
else if (num < viFirst[i]){
sum += i - 1;
break;
}
}
*/
sum += find2(viFirst, num);
num++;
}
}
int main()
{
int n = 0;
vector<int> viF, viFirst;
//build(viF);
build2(viFirst);
//cout << "build up" << endl;
while (cin >> n){
if (n == 0) break;
cout << find2(viFirst, n) << endl;
}
return 0;
}
/*
100
9999
123456
1000000000
*/