只有 2 和 5 作为其数字的特殊数字

Special Numbers having only 2 and 5 as its digit

本文关键字:数字 只有      更新时间:2023-10-16

特殊数字是一个仅由2和5作为其数字的数字,例如-2,5,22,25,52,555等。有一个函数 f(k(,它返回大于或等于 k 的最小数字,是一个特殊数字。

我的问题是,如果有两个数字L和R,我们需要找到f(k(的总和,其中L<=k<=R。我在 c++ 中解决它的方法如下:

#include <bits/stdc++.h>
using namespace std;
long long int power(long long int a, long long int b)
{
long long int prod = 1;
for (long long int i = 1; i <= b; i++)
prod *= a;
return prod;
}
long long int f(long long int k)
{
long long int nod = 0;
long long int temp = 0, carry = 0;
while (k)
{
if (k == 1)
{
temp = 0;
for (int i = nod; i >= 0; i--)
temp = temp * 10 + 2;
return temp;
}
if (((k % 10) + carry) <= 2)
{
temp = 2 * power(10, nod) + temp;
carry = 0;
}
if (((k % 10) + carry) > 2 && ((k % 10) + carry) <= 5)
{
temp = 5 * power(10, nod) + temp;
carry = 0;
}
if (((k % 10) + carry) > 5 && ((k % 10) + carry) <= 9)
{
temp = 2 * power(10, nod) + temp;
carry = 1;
}
k = k / 10;
nod++;
}
if (carry == 1)
temp = 2 * power(10, nod) + temp;
k = temp;
return k;
}
int main()
{
long long int t;
cin >> t;
while (t--)
{
long long int l, r;
cin >> l;
cin >> r;
long long int sum = 0;
for (long long int i = l; i <= r; i++)
sum += f(i);
cout << sum << endl;
}
return 0;
}

但它没有按预期工作,对于许多测试用例,它超过了时间限制。 对于这个问题,什么是正确和最佳的方法?

这个问题来自hackerearth。

https://www.hackerearth.com/pt-br/problem/algorithm/ozs-cool-numbers-a97d4b77-4f0585e3/#:~:text=Special%20numbers%20are%20positive%20integers,n%20d%20265%20are%20not.&text=For%20each%20test%20case%2C%20print%20the%20required%20answer.&text=Time%20Limit%3A%201%2C0%20sec,s(%20for%20each%20input%20file.

我尝试过这个,它似乎有效。待同行评审。

一般的方法是找到适合您的起始号码之上的最小特殊数字,然后将该特殊数字乘以数字数量,直到您点击下一个特殊数字。由于两个特殊数字之间的每个数字都将映射到相同的特殊数字,因此您只需为每个范围计算一次每个特殊数字。

一个 64 位无符号整数可以表示仅由最多 19 位的 2 和 5 组成的十进制数。因此,对下一个特殊数字的每次搜索都是输入数字中十进制数字数的对数(以 2 为基数(。sum 循环的迭代次数在输入数字的十进制位数中也是对数(以 2 为底(,这使得总复杂度O(log 2N)

  1. 设置sum = 0
  2. 给定起始编号L,设置next_k = L
  3. 求不小于next_k的最小特殊数;special = f(next_k)
  4. 设置k = next_k
  5. 设置next_k = min(special, R) + 1.这是使用迭代中常见的"一过完"成语
  6. 设置distance = next_k - k
  7. sum = sum + distance * special
  8. 如果next_k <= R,则转到 3。
  9. 做。返回sum.

当然,如何在给定k之上"安装"一系列 2 和 5 的问题需要回答。我的方法是从可以用 64 位(连续 19 个 5(表示的最大十进制 5 系列开始。

  1. 将此 5 掩码向下移动 10 倍,直到其宽度与输入数字相同
  2. 从最高有效数字开始,遍历数字并测试将该数字设置为 2 是否仍"适合"输入数字。如果是这样,请摆动它。
/*
A special number is a decimal number such that its
digits are only 2 or 5, e.g. {2, 5, 22, 25, 52, 55, ...}
Write a function, f( ), for any non-special number, k, that returns
the smallest special number that is greater than or equal to k
Given a range [L, R], find the sum of all f(k) for each k such
that L <= k <= R
*/
#include <algorithm>
#include <iomanip>
#include <iostream>
using value_t = unsigned long long;
static constexpr value_t MAX_FIVES = 5'555'555'555'555'555'555ull;
static constexpr value_t MAX_THREE_MASK = 3'000'000'000'000'000'000ull;
value_t least_special_no_less_than(value_t k)
{
value_t special_number = MAX_FIVES;
value_t three_mask = MAX_THREE_MASK;
if (k == 0)
{
special_number = 2;
}
else
{
// shift the 555... mask down as low as it can go
while (special_number / 10 >= k)
{
special_number /= 10;
three_mask /= 10;
}
// twiddle each digit from 5->2 to fit the sheet onto the bed
while (three_mask)
{
if (special_number - three_mask >= k)
special_number -= three_mask;
three_mask /= 10;
}
}
return special_number;
}
int main()
{
std::cout << "A special number is one with digits only 2 or 5, e.g. {2, 5, 22, 25, 55, 222, ...}n"
<< "For a number, k, f(k) returns the lowest special number gte kn"
<< "Enter two numbers L and R, this program calculates sum(f(k)) for all k s.t. L <= k <= Rn"
<< "n"
<< "Enter L followed by R: ";
value_t L, R;
std::cin >> L >> R;
value_t sum = 0;
value_t k, next_k = L;
std::cout << std::right
<< std::setw(20) << "k" << " "
<< std::setw(20) << "next_k" << " "
<< std::setw(20) << "special" << " "
<< std::setw(20) << "running sum" << " "
<< "n";
do
{
value_t special = least_special_no_less_than(next_k);
k = next_k;
next_k = std::min(special, R) + 1;
sum += (next_k - k) * special;
std::cout
<< std::setw(20) << k << " "
<< std::setw(20) << next_k << " "
<< std::setw(20) << special << " "
<< std::setw(20) << sum << " "
<< "n";
} while (next_k <= R);
std::cout << std::left
<< "nSum of all special numbers between [" << L << ", " << R << "]: "
<< sum << "n";
}

这给出了以下示例输出:

A special number is one with digits only 2 or 5, e.g. {2, 5, 22, 25, 55, 222, ...}
For a number, k, f(k) returns the lowest special number gte k
Enter two numbers L and R, this program calculates sum(f(k)) for all k s.t. L <= k <= R
Enter L followed by R: 12 47
k               next_k              special          running sum
12                   23                   22                  242
23                   26                   25                  317
26                   48                   52                 1461
Sum of all special numbers between [12, 47]: 1461

为了找到至少等于给定数字的下一个特殊数字,我将提取该数字的十进制数字。然后从最高阶数字开始:

如果一个数字大于 5,
  • 那么这个数字和所有后面的数字都会得到值 2,并且应该增加前一个数字(请注意,如果前一个数字是 5,我们需要迭代,如果我们在第一个数字,我们需要添加一个新数字(
  • 如果数字是 5,请保留它并继续下一个
  • 数字
  • 如果为 3 或 4,则此值为 5,以下所有
  • 值均为 2
  • 如果为 2,请保留它并继续下一个
  • 如果为 0 或 1,则此一个和所有后续值将为 2

出于效率原因,我会计算该数字,在当前数字小于或相等时将其相加,然后通过增加其最低有效数字来计算下一个特殊数字。

在下面的代码中,我选择将所有特殊数字处理封装在一个包含数字值向量和数字本身的类中:

#include <vector>
#include <iostream>
class Special {
public:
long val;
std::vector<char> digits;
// ctor builds first special number at least equal to val       
Special(long val) {
std::vector <char> d;
int n=0;
while (val > 0) {                // extract all decimal digits
d.push_back(val % 10);
val /= 10;
n++;
}
digits = std::vector<char>(n, 2); // initialize the special number with 2
// and start processing starting from most significant digit
while (n-- > 0) {
if (d[n] > 5) {
digits[n] = 6;
normalize(n);
return;
}
else if (d[n] > 2) {
digits[n] = 5;
if (d[n] != 5) {
normalize(digits.size());
return;
}
}
else {
digits[n] = 2;
if (d[n] != 2) {
normalize(digits.size());
return;
}
}
}
normalize(digits.size());
}
Special& normalize(int n) {
/* compute the binary value, and process carries starting from nth digit */
// First process carries
int ndigits = digits.size();
while (n < ndigits) {
if (digits[n] > 5) {
digits[n] = 2;
if (n == ndigits - 1) {
digits.push_back(2);
}
else {
digits[n+1] += 1;
}
}
else {
if (digits[n] > 2) {
digits[n] = 5;
}
else {
digits[n] = 2;
}
break;
}
n++;
}
// compute the binary value
val = 0;
for (auto ix=digits.rbegin(); ix!= digits.rend(); ix++) {
val = 10 * val + *ix;
}
return *this;
}
Special& next() {
/* increase to next special number */
digits[0] += 1;       // just increase least significant digit
return normalize(0);  // and normalize
}
};
int main() {
// read N
int N;
std::cin >> N;
long l,r, tot;
for (int i=0; i<N; i++) {
std::cin >> l >> r;
tot = 0;
Special s(l);
while(l <= r) {
if (l > s.val) s.next();
tot += s.val;
l += 1;
}
std::cout /* << l << " - " << r << " : " */ << tot << 'n';
}
return 0;
}

也许晚了,但我在这里留下来

对于这个问题,关键是要认识到序列是如何工作的。 只有两位数,2 和 5,这意味着该系列的行为为

  • 2 --->创建 22 和 25
  • 5 --->创建 52 和 55
  • 22 --->创建 222 和 225
  • n --->分别创建 (10n +
  • 2( 和 (10n + 5(

以下是我对使用堆栈以提高效率的问题的实现:

long long nthNiceNumber(int n) 
{
queue<long long> q;
q.push(2);  // init the stack with 2 and 5
q.push(5);

int nE = 2;    // means the back of the queue is the n^th element
while (nE < n) // while doesn't reach the n^th element
{
q.push(q.front() * 10 + 2);
nE++;

if (nE == n) break;   // in one iteration, the queue will have 2 new 
// element,so if the first element appended is enough, break

q.push(q.front() * 10 + 5);
nE++;

q.pop(); // pop the front of the queue that used to create two new element above, since it doesn't relevant anymore
}
int size = q.size();
while (size-- > 1) q.pop(); // the n^th element always at the end of the queue, so pop until only one element left.
return q.front();
}