0

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 ?

Community
  • 1
  • 1
sdrtg ghytui
  • 433
  • 3
  • 12
  • 5
    [You should not cross-post the same question on different SE sites.](https://meta.stackexchange.com/questions/64068/is-cross-posting-a-question-on-multiple-stack-exchange-sites-permitted-if-the-qu/64069#64069) - [1](https://math.stackexchange.com/questions/3161455/how-many-pr-numbers-exist-in-a-given-range) – user202729 Mar 25 '19 at 17:09
  • 2
    @Johan See also the meta question [How competitive programming questions should be asked? - Meta Stack Overflow](https://meta.stackoverflow.com/questions/329741/how-competitive-programming-questions-should-be-asked). – user202729 Mar 25 '19 at 17:20
  • You should add the link of original problem as it may have further information like constraints which are essencial to decide which algorithm to use – juvian Mar 25 '19 at 17:28
  • 2
    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 Mar 25 '19 at 19:01
  • @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 Mar 25 '19 at 19:17
  • @Firexsecred: 10^18 is no problem. The dynamic programming approach would go digit by digit. Only 19 digits to consider. – user2357112 Mar 25 '19 at 19:20
  • @user2357112 how would mod 7 be useful? The only way I could find to tell divisibility by 7 uses the actual values of digits or their actual sum. (Digit sum would be quite limited here and could potentially be a parameter of the program). – גלעד ברקן Mar 26 '19 at 13:01
  • @גלעדברקן: If the old residue class is r, then sticking a digit d on the end produces residue class (r*10+d)%7. – user2357112 Mar 26 '19 at 16:01
  • @user2357112 cool. That would work with residue classes of the actual numbers. I was looking at the residue classes of the digit sum, but will think about your idea. – גלעד ברקן Mar 26 '19 at 17:50
  • @גלעדברקן Any ideas on this classical dynamic programming problem ? : -https://stackoverflow.com/questions/55482902/how-can-i-solve-this-classical-dynamic-programming-problem – sdrtg ghytui Apr 02 '19 at 20:18

3 Answers3

0

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.

juvian
  • 15,875
  • 2
  • 37
  • 38
0

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)

user202729
  • 3,358
  • 3
  • 25
  • 36
  • I don't think I've thought of this in terms of a finite state machine before, but it does provide a nicely general conceptual framework to work in. – user2357112 Mar 26 '19 at 16:08
  • I am very new to digit dp, can somebody explain the solution in detail, I hope @גלעד ברקן has a nice solution :-) – sdrtg ghytui Mar 26 '19 at 16:44
  • @user2357112 note that if we use residue classes, any one prime combination has multiple matching residue class combinations. If the digits 2 and 3 exist in the number, we have 5 * 7 matching residue combinations: [0, 0, 0, 0] to [0, 0, 4, 6] for valid numbers. For prime combination [2], we have 3 * 5 * 7 matching residue combinations. – גלעד ברקן Apr 03 '19 at 11:13
  • @גלעדברקן: Indeed we do. That's not a problem. We track all combinations of residue classes mod 2, 3, 5, and 7, among the other information we're tracking. When we reach the end, we add up all table entries corresponding to valid numbers. This will be a lot of table entries. – user2357112 Apr 03 '19 at 16:52
  • @user2357112 I implemented and posted something along these lines. Perhaps you could notice how to make it even more efficient, building the prefix "inline"? At present, it seems to me we need to build up the prefix enumerating up. For example, given upper bound 2345, we'd add up prefixes x, 2x-9x, 2xx-9xx, 20xx, 22xx, 230x-233x then finish manually. (Assume a valid build-up :) I still don't get the suggestion in this answer of how to mark `is_less_than_r ` in a useful way. – גלעד ברקן Apr 05 '19 at 15:47
  • @גלעדברקן: "is_less_than_r" isn't a useful thing to record, as far as I can tell. A "danger zone" flag could be useful, marking prefixes that match the upper bound. Such prefixes can only be extended with digits less than or equal to the next digit of the upper bound. (Also, I'm imagining treating 1234 and 01234 as distinct prefixes, and starting all prefixes from the first digit of the upper bound.) – user2357112 Apr 05 '19 at 22:19
0

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`)
}
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
  • @Firexsecred I would consider adding the algorithm tag to it to generate more interest. – גלעד ברקן Apr 05 '19 at 20:50
  • I've added the "algorithm" tag as well and received a correct answer as well, can you explain it in simple language as the person who answered thought that I am a pro in trees :( – sdrtg ghytui Apr 06 '19 at 18:14
  • Link:-https://stackoverflow.com/questions/55538119/how-do-i-calculate-the-profit-of-a-given-tree?noredirect=1#comment97792828_55538119 – sdrtg ghytui Apr 06 '19 at 18:14
  • @Firexsecred that solution makes sense if you remember that a tree itself can be defined recursively: a `Tree` is either a `Leaf` or a `Node` with children of type `Tree`. – גלעד ברקן Apr 06 '19 at 20:18
  • I understood the things :-) The only problem is I don't know python, would be very good if you can give me those 4 lines of code in Javascript/Python, can you tell me, what am I thinking is correct? This is what he did in code:- – sdrtg ghytui Apr 07 '19 at 06:10
  • psuedo code:- max1(root-node) { return max(-x, node.value+ sum( a1,a2,a3,a4… ) ); //where a1,a2,a3,a4…. are child nodes of the main-node and we get them by putting them into max1 function. } – sdrtg ghytui Apr 07 '19 at 06:15
  • what is the base condition of your function? What if a node does not have anymore children, what will it do now ?Any idea about the time complexity of this for – sdrtg ghytui Apr 07 '19 at 06:18
  • @ גלעד ברקן thanks, I've understood everything, just want to clear up the time complexity thing :-) – sdrtg ghytui Apr 07 '19 at 06:28
  • @Firexsecred try to count or deduct how many times each node will be visited. – גלעד ברקן Apr 07 '19 at 12:31
  • I have solved this problem, abut the pr problem I will check up on your solution after 4-days, I am sure it must be great, any thoughts on this problem:-https://stackoverflow.com/questions/55557580/how-to-find-the-pair-of-strings-whose-combination-results-into-a-palindrome?noredirect=1#comment97815703_55557580 I think using trie(s) can efficiently solve our problem? – sdrtg ghytui Apr 07 '19 at 18:07
  • The example I offered here is basically an implementation of a similar (maybe even identical, it's hard for me to tell since I don't fully understand it) idea to the answer here by user202729. – גלעד ברקן Apr 07 '19 at 18:08
  • Any ideas on this problem : - p https://math.stackexchange.com/questions/3219387/how-do-i-solve-this-chessboard-problem – sdrtg ghytui May 09 '19 at 06:39
  • @Firexsecred if we consider all possible moves at the outset as directed edges in a graph, the task is then to remove all edges. We can observe that any one move can only remove, never add edges. Since we want to take advantage of moves that remove a larger number of edges, I would first experiment with a greedy approach, prioritising moves that remove more edges, also preferring moves that start at a vertex (pawn) lower in the board. – גלעד ברקן May 10 '19 at 18:00
  • I have reached the solution to this :- https://stackoverflow.com/questions/56527621/how-to-maximize-the-sum though some corner cases are missing can you help me out ? – sdrtg ghytui Jun 11 '19 at 04:24