0
votes

It is not a homework problem. I am just curious about this problem. And my approach is simple brute-force :-)

My brute-force C++ code:

int main()
{
    ll l,r;
    cin>>l>>r;
    
    ll f=0;
    ll i=l;
    
    while(i<=r)
    {
        ll j=0;
        string s;
        ll c=0;
        s=to_string(i);

        // cout<<s<<" ";

        ll x=s.length();

        if(x==1)
        {
            c=0;
        }
        else 
        {
            j=0;
            //whil
            while(j<=x-2)
            {
                string b,g;

                b="1";
                g="1";
                
                b=s[j];
                g=s[j+1];
                
                ll k1,k2;
                
                k1=stoi(b);
                k2=stoi(g);

                if(__gcd(k1,k2)==1)
                {
                    c=1;
                    break;
                }
                
                j++;
            }
        }
        
        ll d=0;
        j=0;
        while(j<=x-1)
        {
            if( s[j]=='2' || s[j]=='3' || s[j]=='5' || s[j]=='7')
            {
                string b;
                b="1";
                b=s[j];
                ll k1=stoi(b);
                if(i%k1==0)
                {
                    //d=0;
                }
                else
                {
                    d=1;
                    break;
                }
            }
            j++;
        }
        if(c==1 || d==1)
        {
            // cout<<"NO";
        }
        else
        {
            f++;
            // cout<<"PR";
        }
        // cout<<"\n";
        
        i++;
    }
    
    cout<<f;
    
    return 0;
}

You are given 2 integers 'L' and 'R' . You are required to find the count of all the PR numbers in the range 'L' to 'R' inclusively. PR number are the numbers which satisfy following properties:

  1. No pair of adjacent digits are co-prime i.e. adjacent digits in a PR number will not be co-prime to each other.

  2. PR number is divisible by all the single digit prime numbers which occur as a digit in the PR number.

Note: Two numbers 'a' and 'b' are co-prime, if gcd(a,b)=1.

Also, gcd(0,a)=a;

Example:
Input: [2,5].
Output: '4'.

(Note: '1' is not a prime-number, though its very common)

(All the integers: '2','3','4','5') satisfy the condition of PR numbers :-)

Constraints on 'L','R': 1 <= L, R <= 10^18

What can be the the most efficient algorithm to solve this ?

3
You should add the link of original problem as it may have further information like constraints which are essencial to decide which algorithm to usejuvian
This is a standard case for dynamic programming. You'd want separate counters for each combination of last digit, residue classes mod 3 and 7, and single-digit primes used.user2357112 supports Monica
@user2357112 DP might not work as the range can be as large as 10^18 , but if we can find out some recurrence relation, then we can apply matrix-exponentiation :-) Can you shed more light to the dp-part?:-)sdrtg ghytui

3 Answers

0
votes

Note: This will solve only part 1 which is No pair of adjacent digits are co-prime i.e. adjacent digits in a PR number will not be co-prime to each other.

Here is a constructive approach in python: instead of going throught all numbers in range and filtering by conditions, we will just construct all numbers that satisfy the condition. Note that if we have a valid sequence of digits, for it to continue being valid only the rightmost digit matters in order to decide what the next digit will be.

def ways(max_number, prev_digit, current_number):
    if current_number > max_number:
        return 0
    count = 1
    if prev_digit == 0:
        if current_number != 0:
            count += ways(max_number, 0, current_number * 10)
        for i in range(2, 10): 
            count += ways(max_number, i, current_number * 10 + i)
    if prev_digit == 2 or prev_digit == 4 or prev_digit == 8:
        for i in [0, 2, 4, 6, 8]:
            count += ways(max_number, i, current_number * 10 + i)
    if prev_digit == 3 or prev_digit == 9:
        for i in [0, 3, 6, 9]:
            count += ways(max_number, i, current_number * 10 + i)
    if prev_digit == 5 or prev_digit == 7:
        count += ways(max_number, 0, current_number * 10)
        count += ways(max_number, prev_digit, current_number * 10 + prev_digit)
    if prev_digit == 6:
        for i in [0, 2, 3, 4, 6, 8, 9]:
            count += ways(max_number, i, current_number * 10 + i)
    return count

As we are generating all valid numbers up to max_number without any repeats, the complexity of this function is O(amount of numbers between 0 and max_number that satisfy condition 1). To calculate the range a to b, we just need to do ways(b) - ways(a - 1).

Takes less than 1 second to caculate these numbers from 0 to 1 million, as there are only 42935 numbers that satisfy the result. As there are few numbers that satisfy the condition, we can then check if they are multiple of its prime digits to satisfy also condition 2. I leave this part up to the reader as there are multiple ways to do it.

0
votes

TL;DR: This is more commonly called "digit dynamic programming with bitmask"

In more competitive-programming-familiar terms, you'd compute dp[n_digit][mod_2357][is_less_than_r][digit_appeared][last_digit] = number of numbers with n_digit digits (including leading zeroes), less than the number formed by first n_digit digits of R and with the other properties match. Do it twice with R and L-1 then take the difference. The number of operations required would be about 19 (number of digits) * 210 (mod) * 2 * 24 (it's only necessary to check for appearance of single-digit primes) * 10 * 10, which is obviously manageable by today computers.


Think about how you'd check whether a number is valid.

Not the normal way. Using a finite state automaton that take the input from left to right, digit by digit.

For simplicity, assume the input has a fixed number of digits (so that comparison with L/R is easier. This is possible because the number has at most as many digits as R).

It's necessary for each state to keep track of:

  • which digit appeared in the number (use a bit mask, there are 4 1-digit primes)
  • is the number in range [L..R] (either this is guaranteed to be true/false by the prefix, otherwise the prefix matches with that of L/R)
  • what is the value of the prefix mod each single digit prime
  • the most recent digit (to check whether all pairs of consecutive digits are coprime)

After the finite state automaton is constructed, the rest is simple. Just use dynamic programming to count the number of path to any accepted state from the starting state.


Remark: This method can be used to count the number of any type of object that can be verified using a finite state automaton (roughly speaking, you can check whether the property is satisfied using a program with constant memory usage, and takes the object piece-by-piece in some order)

0
votes

We need a table where we can look up the count of suffixes that would match a prefix to construct valid numbers. Given a prefix's

right digit
prime combination
mod combination

and a suffix length, we'd like the count of suffixes that have searchable:

left digit
length
prime combination
mod combination

I started coding in Python, then switched to JavaScript to be able to offer a snippet. Comments in the code describe each lookup table. There are a few of them to allow for faster enumeration. There are samples of prefix-suffix calculations to illustrate how one can build an arbitrary upper-bound using the table, although at least some, maybe all of the prefix construction and aggregation could be made during the tabulation.

function gcd(a,b){
  if (!b)
    return a
  else
    return gcd(b, a % b)
}

// (Started writing in Python,
// then switched to JavaScript...
// 'xrange(4)' -> [0, 1, 2, 3]
// 'xrange(2, 4)' -> [2, 3]
function xrange(){
  let l = 0
  let r = arguments[1] || arguments[0]
  if (arguments.length > 1)
    l = arguments[0]
  return new Array(r - l).fill(0).map((_, i) => i + l)
}

// A lookup table and its reverse,
// mapping each of the 210 mod combinations,
// [n % 2, n % 3, n % 5, n % 7], to a key
// from 0 to 209.
// 'mod_combs[0]' -> [0, 0, 0, 0]
// 'mod_combs[209]' -> [1, 2, 4, 6]
// 'mod_keys[[0,0,0,0]]' -> 0
// 'mod_keys[[1,2,4,6]]' -> 209
let mod_combs = {}
let mod_keys = {}
let mod_key_count = 0
for (let m2 of xrange(2)){
  for (let m3 of xrange(3)){
    for (let m5 of xrange(5)){
      for (let m7 of xrange(7)){
        mod_keys[[m2, m3, m5, m7]] = mod_key_count
        mod_combs[mod_key_count] = [m2, m3, m5, m7]
        mod_key_count += 1
      }
    }
  }
}

// The main lookup table built using the
// dynamic program
// [mod_key 210][l_digit 10][suffix length 20][prime_comb 16]
let table = new Array(210)
for (let mk of xrange(210)){
  table[mk] = new Array(10)
  for (let l_digit of xrange(10)){
    table[mk][l_digit] = new Array(20)
    for (let sl of xrange(20)){
      table[mk][l_digit][sl] = new Array(16).fill(0)
    }
  }
}

// We build prime combinations from 0 (no primes) to
// 15 (all four primes), using a bitmask of up to four bits.
let prime_set = [0, 0, 1<<0, 1<<1, 0, 1<<2, 0, 1<<3, 0, 0]

// The possible digits that could
// follow a digit
function get_valid_digits(digit){
  if (digit == 0)
    return [0, 2, 3, 4, 5, 6, 7, 8, 9]
  else if ([2, 4, 8].includes(digit))
    return [0, 2, 4, 6, 8]
  else if ([3, 9].includes(digit))
    return [0, 3, 6, 9]
  else if (digit == 6)
    return [0, 2, 3, 4, 6, 8, 9]
  else if (digit == 5)
    return [0, 5]
  else if (digit == 7)
    return [0, 7]
}

// Build the table bottom-up

// Single digits
for (let i of xrange(10)){
  let mod_key = mod_keys[[i % 2, i % 3, i % 5, i % 7]]
  let length = 1
  let l_digit = i
  let prime_comb = prime_set[i]
  table[mod_key][l_digit][length][prime_comb] = 1
}

// Everything else
// For demonstration, we just table up to 6 digits
// since either JavaScript, this program, or both seem
// to be too slow for a full demo.
for (let length of xrange(2, 6)){
  // We're appending a new left digit
  for (let new_l_digit of xrange(0, 10)){
    // The digit 1 is never valid
    if (new_l_digit == 1)
      continue

    // The possible digits that could
    // be to the right of our new left digit      
    let ds = get_valid_digits(new_l_digit)

    // For each possible digit to the right
    // of our new left digit, iterate over all
    // the combinations of primes and remainder combinations.
    // The ones that are populated are valid paths, the
    // sum of which can be aggregated for each resulting
    // new combination of primes and remainders.
    for (let l_digit of ds){
      for (let p_comb of xrange(16)){
        for (let m_key of xrange(210)){
          new_prime_comb = prime_set[new_l_digit] | p_comb
          // suffix's remainder combination
          let [m2, m3, m5, m7] = mod_combs[m_key]
          // new remainder combination
          let m = Math.pow(10, length - 1) * new_l_digit
          let new_mod_key = mod_keys[[(m + m2) % 2, (m + m3) % 3, (m + m5) % 5, (m + m7) % 7]]

          // Aggregate any populated entries into the new
          // table entry
          table[new_mod_key][new_l_digit][length][new_prime_comb] += table[m_key][l_digit][length - 1][p_comb]
        }
      }
    }
  }
}


// If we need only a subset of the mods set to
// zero, we need to check all instances where
// this subset is zero. For example,
// for the prime combination, [2, 3], we need to
// check all mod combinations where the first two
// are zero since we don't care about the remainders
// for 5 and 7: [0,0,0,0], [0,0,0,1],... [0,0,4,6]
// Return all needed combinations given some
// predetermined, indexed remainders.
function prime_comb_to_mod_keys(remainders){
  let mod_map = [2, 3, 5, 7]
  let mods = []
  for (let i of xrange(4))
    mods.push(!remainders.hasOwnProperty(i) ? mod_map[i] - 1 : 0)
  
  function f(ms, i){
    if (i == ms.length){
      for (let idx in remainders)
        ms[idx] = remainders[idx]
      return [mod_keys[ms]]
    }    
    let result = []
    for (let m=ms[i] - 1; m>=0; m--){
      let _ms = ms.slice()
      _ms[i] = m
      result = result.concat(f(_ms, i + 1))
    }
    return result.concat(f(ms, i + 1))
  }
  return f(mods, 0)
}

function get_matching_mods(prefix, len_suffix, prime_comb){
  let ps = [2, 3, 5, 7]
  let actual_prefix = Math.pow(10, len_suffix) * prefix
  let remainders = {}
  for (let i in xrange(4)){
    if (prime_comb & (1 << i))
      remainders[i] = (ps[i] - (actual_prefix % ps[i])) % ps[i]
  }
  return prime_comb_to_mod_keys(remainders)
}

// A brute-force function to check the
// table is working. Returns a list of
// valid numbers of 'length' digits
// given a prefix.
function confirm(prefix, length){
  let result = [0, []]
  let ps = [0, 0, 2, 3, 0, 5, 0, 7, 0, 0]
  let p_len = String(prefix).length

  function check(suffix){
    let num = Math.pow(10, length - p_len) * prefix + suffix
    let temp = num
    prev = 0
    while (temp){
      let d = temp % 10
      if (d == 1 || gcd(prev, d) == 1 || (ps[d] && num % d))
        return [0, []]
      prev = d
      temp = ~~(temp / 10)
    }
    return [1, [num]]
  }

  for (suffix of xrange(Math.pow(10, length - p_len))){
    let [a, b] = check(suffix)
    result[0] += a
    result[1] = result[1].concat(b)
  }
  return result
}

function get_prime_comb(prefix){
  let prime_comb = 0
  while (prefix){
    let d = prefix % 10
    prime_comb |= prime_set[d]
    prefix = ~~(prefix / 10)
  }
  return prime_comb
}

// A function to test the table
// against the brute-force method.
// To match a prefix with the number
// of valid suffixes of a chosen length
// in the table, we want to aggregate all
// prime combinations for all valid digits,
// where the remainders for each combined
// prime combination (prefix with suffix)
// sum to zero (with the appropriate mod).
function test(prefix, length, show=false){
  let r_digit = prefix % 10
  let len_suffix = length - String(prefix).length
  let prefix_prime_comb = get_prime_comb(prefix)

  let ds = get_valid_digits(r_digit)
  let count = 0

  for (let l_digit of ds){
    for (let prime_comb of xrange(16)){
      for (let i of get_matching_mods(prefix, len_suffix, prefix_prime_comb | prime_comb)){
        let v = table[i][l_digit][len_suffix][prime_comb]
        count += v
      }
    }
  }

  let c = confirm(prefix, length)
  
  return `${ count }, ${ c[0] }${ show ? ': ' + c[1] : '' }`
}

// Arbitrary prefixes
for (let length of [3, 4]){
  for (let prefix of [2, 30]){
    console.log(`prefix, length: ${ prefix }, ${ length }`)
    console.log(`tabled, brute-force: ${ test(prefix, length, true) }\n\n`)
  }
}

let length = 6
for (let l_digit=2; l_digit<10; l_digit++){
  console.log(`prefix, length: ${ l_digit }, ${ length }`)
  console.log(`tabled, brute-force: ${ test(l_digit, length) }\n\n`)
}