6

[Requirement]
Given is an alphabet {0, 1, ... , k}, 0 ≤ k ≤ 9. We say that a word of length n over this alphabet is tight if any two neighbor digits in the word do not differ by more than 1. Input is a sequence of lines, each line contains two integer numbers k and n, 1 ≤ n ≤ 100. For each line of input, output the percentage of tight words of length n over the alphabet {0, 1, ... , k} with 5 fractional digits.

[Input]

4 1
2 5
3 5
8 7

[Output]

100.00000
40.74074
17.38281
0.10130

First, I can't understand this quiz. Example, if the input is 2, 5. I don't know why the answer is 40.74074.

In this situation, if it will be "tight". The middle number has to be 1.

Example,

00000 00001 00002
00010 00011 00012
....

So,

All of the case in here is, 35 = 243

And the last digit has to be 1, so 34 = 81 will be the "tight" case.

So, the output has to be 81/243 = 0.33333333333333333 = 33.333333%

Did i missed something?

And any good algorithm to solve this?

9
  • I don't know if your examples go far enough. E.g. on the second line, it should have 00010. Did you miss that case, or are you including it? Commented Sep 3, 2013 at 23:50
  • 1
    You say "The middle number has to be 1" and "The last digit has to be 1", but both these statements are wrong. E.g. 00000 is tight for k=2, n=5, since if you pick any 2 adjacent digits (say digit #2 and digit #3), they are both 0, so they have a difference of 0 which is <= 1. Commented Sep 3, 2013 at 23:53
  • For the k=2, n=5 case, the only words that are not tight are those containing the substring 02 or the substring 20. Also: one powerful technique for solving problems like this is called generating functions: en.wikipedia.org/wiki/Generating_function Commented Sep 3, 2013 at 23:56
  • Thanks for the reply. But still i don't understand :( How could 00010 can be tight? between 00010 is 00002 00010 00011. the right side is correct, but the left side cannot be a tight. Commented Sep 4, 2013 at 0:02
  • 2
    @StevePark I'm not sure what you mean by your last comment. 00010 is just a string of characters, as j_random_hacker said. You don't have to worry about its actual numerical value. 00002 is indeed one less than 00010, but that doesn't mean anything in this context. Commented Sep 4, 2013 at 0:08

5 Answers 5

7

Simplify this problem by generalizing it

(Sorry, I swapped the order of k and n.)

If you leave off the last digit of a tight number, you get another tight number, and their last digits differ by at most 1.

Assume you have all the numbers c(n, k, l) of tight numbers of length n with last digit l. Then the number of tight numbers of length n + 1 and last digit l is c(n + 1, k, l) = c(n, k, l - 1) + c(n, k, l) + c(n, k, l + 1).

The base case is simple: n=1 means one tight number, i.e. c(1, k, l) = 1.

Test (Python):

def c(n, k, l):
    if l > k or l < 0:
        return 0
    if n == 1:
        return 1
    return sum(c(n - 1, k, i) for i in range(l - 1, l + 2))

def f(n, k):
    tight = sum(c(n, k, l) for l in range(k + 1))
    return tight / (k + 1) ** n

Examples:

>>> print(f(1,4))
1.0
>>> print(f(4, 1))
1.0
>>> print(f(5, 2))
0.4074074074074074
>>> print(f(5, 3))
0.173828125
>>> print(f(7, 8))
0.0010129691411338857

For really big numbers, this becomes slow, because the same numbers are computed over and over. These can be cached ("memoized") by adding the following two lines at the start of the program (the second lines decorate the following function c(n, k, l) with a cache):

import functools
@functools.lru_cache()

Example:

>>> f(100,9)
1.0051226793648084e-53
Sign up to request clarification or add additional context in comments.

10 Comments

Your algorithm seems to be wrong, at least your output for 7,8 is not matched to the desired output of OP.
@SaeedAmiri: Just like the other examples, the output is not rounded to 5 decimals (and not even a percentage, but a fraction). I beleive that formatting the output does not improve the answer (in this case).
I'm not talking about rounding, just look at your output, is : 0.001.... how do you want to round it to 0.1.... ?
@SaeedAmiri Please see this page: mathsisfun.com/converting-fractions-percents.html
Sorry, I thought all others are .4..., ... I didn't see OP wrote it in percentage.
|
4

My reading is a little different from yours: as I understand it, the first number is the size of the alphabet, and the second is the length of the words over that alphabet that one must consider, so:

4 1 => 100%

Seems like a matter of definition; the likely rationale is that since the digits in words of length 1 do not have any neighbors, they cannot differ from them by more than 1, independent of the size of the alphabet, so words of length 1 are considered “tight” by definition.

2 5 => 40.74074%

So this is words of length 5 over a ternary (3-digit) alphabet, {0,1,2}. There are, as you observe, 3^5 possible such words. The non-tight words are those (where x means “don't care”) like “xxx02”, “xxx20”, “xx02x”, “xx20x”, “x02xx”, “x20xx”, “02xxx” and “20xxx” which have a 2 adjacent to a zero. Each of these 8 patterns has 27 variations (there are 3 x's in each case, and each can have any of 3 values), but of course there's a lot of overlap: “02020” ends up in 3 of them.

So, if I understand correctly, in the absence of any short-cuts, the solution must be to generate all the combinations, examine pairs of adjacent digits in every combination (you can bug out early once you know that a word is not tight), and then count either the number of tight or non-tight words (either gives you the other, since you know the total size of the set.

3 Comments

This is very inefficient way, problem is not generating them, in worst case is counting them (and I think there is more elegant way than counting).
Sure, WolframH's solution is clearly far superior. I was more concerned with explaining my understanding of the question and what I saw as the OP's misinterpretation of it.
I know what you mean, I just want to notify that this is not a good approach to semi counting problem.
2

Here's some ruby code whose output matches the sample data:

#!/usr/bin/env ruby

def isTight( x )
  for i in (1..x.length-1)
    return false if 1 < (x[i].to_i-x[i-1].to_i).abs
  end
  return true
end

def getWord( x, base, n )
  retval = []
  1.upto(n) do
    x, r = x.divmod(base)
    retval.unshift r
  end
  retval.join
end

def percent( k, n )
  nwords = (k+1) ** n
  count = 0
  for i in (0..nwords-1)
    word = getWord( i, k+1, n )
    count += 1 if isTight( word )
  end
  return 100.0 * count / nwords
end

STDIN.each_line do |line|
  line.chomp!
  puts line+' '+percent(*line.split(' ').map { |i| i.to_i }).to_s
end

This accepts the 4 lines

4 1
2 5
3 5
8 7

as input, and outputs

4 1 100.0
2 5 40.74074074074074
3 5 17.3828125
8 7 0.10129691411338856

(sorry not 5 decimal places)


EDIT: In actual practice, you would most certainly want to use WolframH's recursive solution, included here for completeness:

#!/usr/bin/env ruby

$cache = Hash.new
def count( k, n, last )
  key = "#{k}:#{n}:#{last}"
  return $cache[key] if $cache.has_key?(key)
  return 0 if !(0 <= last && last <= k) # last digit must be in range
  return 1 if n == 1 # single digit numbers are always tight
  return $cache[key] = (-1..1).inject(0) { |sum,i| sum + count(k,n-1,last+i) }
end

def percent( k, n )
  ntight = (0..k+1).inject(0) { |sum,last| sum + count(k,n,last) }
  return 100.0 * ntight / (k+1)**n
end

puts percent( 1, 4 )
puts percent( 2, 5 )
puts percent( 3, 5 )
puts percent( 8, 7 )
puts percent( 9, 100 )

Using the $cache, this runs extremely fast on a x86_64 Intel(R) Core(TM) i3-3240 CPU @ 3.40GHz:

$ time ./tight.rb
100.0
40.74074074074074
17.3828125
0.10129691411338856
1.0051226793648083e-51

real    0m0.016s
user    0m0.010s
sys     0m0.005s

4 Comments

Algorithm seems to be correct but for n=100 and k=9 I don't think this works.
This is straight away, but very inefficient. And this is not the way the person who thought of this question was thinking about...
@Juto Yes I forgot to add the disclaimer that this is not the fastest algorithm. The OP's question involved understanding the problem, rather than asking for the most efficient solution, so a concise script to elucidate the problem was the goal here. Nevertheless I take your comment as a good reminder that time/memory efficiency should always be addressed here on SO regardless of context :)
Updated with WolframH's elegant recursive solution.
2

Our problem is to find the number of tight words with length n, ie a[1 .. n]. Below is a solution based on dynamic programming. The idea is to assume that we have the answer up to length i - 1, we construct an equation to calculate answer for length i.

Let C(i, d) is the total number of tight words that has length i, ie a[1 .. i], with the final digit a[i] = d, 0 <= d <= k. Observing that a[i - 1] - 1 <= a[i] <= a[i - 1] - 1 (definition of tight word), we have the following recursive relationship:

For i = 1: 
  C(1, d) = 1

For i > 1: 
  C(i, d) = 
    C(i - 1, 0) + C(i - 1, 1) -- if d == 0
    C(i - 1, k - 1) + C(i - 1, k) -- if d == k
    C(i - 1, d - 1) + C(i - 1, d) + C(i - 1, d + 1) -- otherwise

Then what we're after is simply:

N(n) = C(n, 0) + C(n, 1) + ... C(n, k)

CODE:

And this is a nodejs program that was tested to generate same answers in your sample input (it's not yet dynamic programming since I haven't cache C(i, p) -- there are lot of repeated calculations, but should be easy to do that)

// tight_words.js

var k = 2;
var n = 5;

function N(i) {
    var n = 0;

    for (d = 0; d <= k; ++d)
        n += C(i, d);

    return n;
}

function C(i, d) {
    if (i == 1)
        return 1;

    if (d == 0)
        return C(i - 1, 0) + C(i - 1, 1);

    if (d == k)
        return C(i - 1, k - 1) + C(i - 1, k);

    return C(i - 1, d - 1) + C(i - 1, d) + C(i - 1, d + 1);
}

var total = Math.pow(k + 1, n);
var c = N(n);
console.log('k = ' + k + ', n = ' + n);
console.log('==> percentage = ' + c / total);

1 Comment

Yeah, I didn't read your answer when I wrote this. Python scared me off I guess, and I was abit eager to start thinking since this is a nice classic kind of problem :)
0

Based on WolframH's answer, I tried the problem for the sample inputs in C++ and it seems to work. I also tried the phython solution, which worked fine with the sample inputs. What comes interesting is, when I increased the input to a bit bigger numbers (i.e., 3 and 18), both solutions I have in C++ and in phython hang for indeterminate time.

What has gone wrong?

Very coincidentally I just happened to go through my Dynamic Programming notes yesterday night, and read about the Weighted Independent Set Problem. Aha! We are doing far too much work than we are supposed to! In:

#include <math.h>
#include <iomanip>
#include <iostream>
using namespace std;

void get_tight_number(int base_max, int length)
{
  double result = 0;
  int _tight_numbers = 0;
  double total = pow(base_max + 1, length);
  for (int i = 0; i <= base_max; ++i)
  {
    _tight_numbers += get_tight_number_help(base_max, length, i);
  }
  result = 100 * _tight_numbers / total;
  cout << fixed << setprecision(5) << result << "\n";
}

int get_tight_number_help(int base_max, int length, int endwith)
{
  cout << "Length: " << length << "endwith: " << endwith << endl;
  if (length < 1 || endwith < 0 || endwith > base_max)
    return 0;
  if (length == 1)
  {
    return 1;
  } else 
  {
    return get_tight_number_help(base_max, length - 1, endwith)
         + get_tight_number_help(base_max, length - 1, endwith + 1)
         + get_tight_number_help(base_max, length - 1, endwith - 1);
  }
}

int main()
{
  get_tight_number(8, 7);
  return 0;
}

With bunch of prints and correct result 0.10130. If I do grep "endwith:" | wc -l I get 7719, which means, for this input, the helper function was called more than 7000 times! To just have an idea how many times it is called on other inputs, I got:

Input    #
8, 8     22254
8, 6     2682
8, 5     933

not very good... We are doing too much recalculation. Instead I put together the bottom up solution for the reference array:

int** tight_number_bottom_up(int base_max, int length)
{
  int** result = new int*[base_max + 1];
  for (int i = 0; i < base_max + 1; ++i)
  {
    result[i] = new int[length];
  }
  //Ends with i, i.e., looping over them
  for (int j = 0; j < length + 1; ++j)
  {
    for (int i = 0; i < base_max + 1; ++i)
    {
      if (j == 0)
      {
        result[i][j] = 0;
      } else if (j == 1)
      {
        result[i][j] = 1;
      } else
      {
        int bigger = i == base_max ? 0 : result[i + 1][j - 1];
        cout << "bigger: " << bigger << endl;
        int smaller = i == 0 ? 0 : result[i - 1][j - 1];
        cout << "smaller: " << smaller << endl;
        result[i][j] = result[i][j - 1] + bigger + smaller;
      }
    }
  }
  return result;
}

I am sure that the number of iterations in forming the bottom up table is maximum (base_max + 1) * (length + 1), happy that I finished writing and happy that it gives the correct results.

Follow-up question (if you are still with me)

double seems to be not enough for inputs like 9, 100 or even 9, 50, what can I do to make a "long"er double?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.