challenge 正しい文(クイズ)

「この文は0が□個,1が□個,...,9が□個あります」
が正しくなるように□を埋めてください.数値は10進数とします.
一般のn(<=16で可)進数でも解いてみてください.

たとえば2進数なら
「この文は0が11個,1が100個あります」
となります.

Posted feedbacks - Nested

Flatten Hidden
うーん,
10進数のときは
「この文は
  0が□個,
  1が□個,
  2が□個,
  3が□個,
  4が□個,
  5が□個,
  6が□個,
  7が□個,
  8が□個,
  9が□個あります」
の省略形のつもりだったのですが,そうとれちゃいますかね.
もちろんn進数の場合は0からn-1までの数値は少なくとも一つ文中に登場します.

うう、すみません、題意を違えてました。あらためて、yuinさんの方法を拝借・・・

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# coding: cp932

def solve(n):
    assert 2 <= n <= 16
    if n == 2:
        a = ["11", "100"]
    else:
        a = ["1"] * n; a[1] = "11"; a[2] = "2"
    print u"この文は%sあります" % \
        u",".join(u"%s%s個" % (hex(i)[-1], m) for (i, m) in enumerate(a))

def main():
    solve(2)
    solve(10)
    solve(16)

if __name__ == '__main__':
    main()
とりあえず乱暴な方法だけど2進から16進まで一つずつ答えを得た。

この文は0が11個,1が100個あります。
この文は0が1個,1が11個,2が2個あります。
この文は0が1個,1が11個,2が2個,3が1個あります。
...
この文は0が1個,1が11個,2が2個,3が1個,4が1個,5が1個,6が1個,7が1個,8が1個,9が1個あります。
...
この文は0が1個,1が11個,2が2個,3が1個,4が1個,5が1個,6が1個,7が1個,8が1個,9が1個,aが1個,bが1個,cが1個,dが1個,eが1個,fが1個あります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
for($n=2;$n<=16;++$n)
{    $str="";
    do{
        $a=count_chars($str);
        $b=array();
        for($i=0;$i<$n;++$i)
        {    $c=base_convert($i,10,$n);
            $b[]="${c}が".base_convert($a[ord($c)],10,$n)."個";
        }
        $str0=$str;
        $str=implode(",",$b);
    }while($str!=$str0);
    echo "この文は${str}あります。\n";
}
?>
>一つずつ答えを得た。
別解もあるので探してみてください.
可能なら,全て列挙してみてください.
#「解いて下さい」というとそういうものだと思ってたけど,違うんですね.
# あいまいな書き方で申し訳ない.
あ、いえいえ。
多分そうなんだろうと思ったのですが、
とりあえず漸近的な方法でも答えにたどり着く
(しかも総当たりよりかなり速い)ってことで
投稿してみました。

全探索バージョンは探索範囲の絞りこみ中…。

これでいいような気が。

1
2
3
4
5
6
def solve(n:int) = n match {
  case 2 => "0=11, 1=100"
  case n => "0=1, 1=11, 2=2, " + (List[String]() /: (3 until n)) {(s,i) => 
              Integer.toString(i,n)+"=1"::s
            }.reverse.mkString(", ")
}

すみません、日本語がこけるので文章を変えました。

n=10の時で

0=1, 1=11, 2=2, 3=1, 4=1, 5=1, 6=1, 7=1, 8=1, 9=1

という感じで出力します。

ナイーブな総当たりを書いてみましたが、n進数のnが増えると計算量が爆発しますね。あと、解は常に有限個だと思うんですが (ある点以上になると数字の数を増やすには数値の「桁を増やす」しかなくなるが、一桁増やしても数字そのものは1つしか増えないのに、その数値分の数字を確保するにはさらにおよそn個の数字が増えなければならない)、具体的な上限を求めるまでには至りませんでした。コードは無限に順列を生成してチェックしつづけるので実行時に適当にinterruptしてます。どうも3進以上は「1が11個」の解がmaxっぽい?

2進
この文は0が11個, 1が100個あります。

3進
この文は0が10個, 1が10個, 2が2個あります。
この文は0が2個, 1が2個, 2が10個あります。
この文は0が1個, 1が11個, 2が2個あります。

4進
この文は0が1個, 1が2個, 2が3個, 3が2個あります。
この文は0が1個, 1が3個, 2が1個, 3が3個あります。
この文は0が1個, 1が11個, 2が2個, 3が1個あります。

5進
この文は0が1個, 1が3個, 2が2個, 3が3個, 4が1個あります。
この文は0が1個, 1が11個, 2が2個, 3が1個, 4が1個あります。

6進
この文は0が1個, 1が11個, 2が2個, 3が1個, 4が1個, 5が1個あります。

7進
この文は0が1個, 1が4個, 2が3個, 3が2個, 4が2個, 5が1個, 6が1個あります。
この文は0が1個, 1が11個, 2が2個, 3が1個, 4が1個, 5が1個, 6が1個あります。

8進
この文は0が1個, 1が5個, 2が3個, 3が2個, 4が1個, 5が2個, 6が1個, 7が1個あります。
この文は0が1個, 1が11個, 2が2個, 3が1個, 4が1個, 5が1個, 6が1個, 7が1個あります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module Main where
import Numeric
import Char
import System
import qualified System.IO.UTF8 as U

count pred [] = 0
count pred (x:xs) = (if pred x then 1 else 0) + count pred xs

countDigs radix k = let s = map digitToInt $ showIntAtBase radix intToDigit k $ ""
                    in map (\i -> count (==i) s) [0..(radix-1)]
countDigs' radix ks = foldl (\rs cs -> map (uncurry (+)) $ zip rs cs)
                            (replicate radix 0)
                            $ map (countDigs radix) ks

combListsM 1 lim     = [[lim]]
combListsM n lim = [x:xs | x <- [1..(lim-1)], xs <- combListsM (n-1) lim]
                   ++ [lim:xs | xs <- combListsS (n-1) lim]
                   ++ [lim:xs | xs <- combListsM (n-1) lim]
combListsS 1 lim     = [[x] | x <- [1..(lim-1)]]
combListsS n lim = [x:xs | x <- [1..(lim-1)], xs <- combListsS (n-1) lim]

combLists' n lim = combListsM n lim ++ combLists' n (lim+1)

combLists'' n = combLists' n 1

check radix (xs:xss) = let xs' = reverse xs
                           cs = map (+1) $ countDigs' radix xs'
                       in if and $ map (uncurry (==)) $ zip xs' cs
                          then xs':(check radix xss)
                          else check radix xss

solve radix = check radix $ combLists'' radix 

showResult radix xs = (foldl ct "この文は" [0..(radix-1)]) ++ "あります。"
  where ct cs i = cs ++ (if i == 0 then "" else ", ")
                     ++ show i ++ "が"
                     ++ showIntAtBase radix intToDigit (xs!!i) "個"

main = do args <- getArgs
          case args of
            [radix] -> case readDec radix of
                         (r,_):_  -> U.putStr
                                            $ unlines
                                            $ map (showResult r)
                                            $ solve r
            _       -> putStrLn "Usage: solve <radix>"
高階関数を使って。
書き換えた部分のみ。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import List
import Maybe

count pred xs = length $ filter pred xs

countDigs' radix ks = map sum $ transpose $ map (countDigs radix) ks

combListsS n lim = sequence $ replicate n [1..lim-1]

combLists'' n = [1..] >>= combListsM n

check radix xss = mapMaybe f xss  where
  f xs = if xs' == cs then Just xs' else Nothing  where
    xs' = reverse xs
    cs = map (+1) $ countDigs' radix xs'

showResult radix xs = ("この文は" ++) $ (++ "あります。")
  $ concat $ intersperse ", "
  $ map ct [0..radix-1]
  where ct i = show i ++ "が" ++ showIntAtBase radix intToDigit (xs!!i) "個"
いい方法が思いつかなかったため多重ループ生成 & eval してしまいました。

入る数字は n=2 なら 8 未満、n>2 なら n^2 未満と評価できました。
というわけで理論上は停止性を保障できています。

;; format の部分はもっときれいにやる方法がないものでしょうか……
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(defun bound (n) (if (= n 2) 8 (* n n)))

(defun solution-p (n &rest nums)
  (let ((s (format nil (format nil "~~{~~~DR~~}" n) nums)))
    (loop for i from 0 below n and a in nums
      unless (= (1- a) (count (digit-char i n) s)) return nil
      finally (return t))))

(defun gen-solver (n i bound chars vars)
  (if (= i n)
      `(if (solution-p ,n ,@vars)
           (format t "この文は~@?あります~%"
                   (format nil "~~@{~~Cが~~~DR個~~^,~~}" ,n)
                   ,@(apply #'nconc (mapcar #'list chars vars))))
    `(loop for ,(nth i vars) from 0 below ,bound do
       ,(gen-solver n (1+ i) bound chars vars))))

(defun solve (n)
  (let ((code (gen-solver n 0 (bound n)
                          (loop for i from 0 below n collect (digit-char i n))
                          (loop repeat n collect (gensym)))))
    ;; (eval code)
    (funcall (compile nil (eval `(lambda () ,code))))
    ))

n>2 のときは n+2 未満しか入らないかな? あと、文に表れる数字の個数を使って枝刈りできますね。 これで n=8 のときが1分で終わりました。 n<=8 での出力結果はすべて shiro さんの #4367 と同じです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(defun bound (n) (if (= n 2) 8 (+ n 2)))

(defun check (n &rest nums)
  (<= (reduce (lambda (s x) (+ s (- x (floor (log x n)) 1))) nums
              :initial-value 0)
      n))

(defun solution-p (n &rest nums)
  (let ((s (format nil (format nil "~~{~~~DR~~}" n) nums)))
    (loop for i from 0 below n and a in nums
      unless (= (1- a) (count (digit-char i n) s)) return nil
      finally (return t))))

(defun gen-solver (n i bound chars vars)
  (if (= i n)
      `(if (solution-p ,n ,@vars)
           (format t "この文は~@?あります~%"
                   (format nil "~~@{~~Cが~~~DR個~~^,~~}" ,n)
                   ,@(apply #'nconc (mapcar #'list chars vars))))
    `(loop for ,(nth i vars) from 1 below ,bound
       ,@(if (> n (1+ i)) `(while (check ,n ,@(subseq vars 0 i))) ()) do
       ,(gen-solver n (1+ i) bound chars vars))))

(defun solve (n)
  (let ((code (gen-solver n 0 (bound n)
                          (loop for i from 0 below n collect (digit-char i n))
                          (loop repeat n collect (gensym)))))
    ;; (eval code)
    (funcall (compile nil (eval `(lambda () ,code))))
    ))
「かな?」とか言ってますが、一応計算して出した評価なので
どうやって出てきたか書いておきます。

正しい文に現れる数字の個数は次の二通りに表せます。
1. Σ"k が現れる回数"
2. n + Σ("k が現れる回数" の n 進での桁数)
そこで k が現れる回数を a[k], その桁数を f[k] とすると
(*) Σ(a[k]-f[k]) = n

これと a[k] >= f[k] から各 k について a[k] - f[k] <= n
左辺は単調増加なことから適当に計算して
 n=2 なら a[k] <= 5
 n>2 なら a[k] <= n + 2
が出ます。

あと n>2 で n+2 が入らないことの確認も。
a[k] = n+2 = 12(n) だと a[k]-f[k] = n なので (*) から
i!=k のとき a[i]=f[i] が必要で、これを満たすのは a[i]=1 だけ。
つまりひとつの数字を除いて一回しか現れないということですが、
12 が出てくる以上これは不可能です。
n >= 7 の場合は以下の2つしかない。

「この文は0が1個, 1が11個, 2が2個, 3が1個, 4が1個, ...,
  (n-1)が1個あります。」
「この文は0が1個, 1が(n-3)個, 2が3個, 3が2個, 4が1個, ...,
  (n-4)が1個, (n-3)が2個, (n-2)が1個, (n-1)が1個あります。」


長いです。ややこしいです。証明に抜けがないとよいですが・・・

kozimaさんの#4374の表記に合わせて i が現れる回数を a[i] と書きます。

■(A) すべての i に対して a[i] <= n+1

kozimaさんの#4374による。

■(B) k != 1 ならば a[k] < n(つまり a[k] は1桁)

(A) より各 a[i] は1桁か、上位桁の数字が 1 の2桁。
よって 1 でない i は1の位にしか現れる可能性がないので a[i] <= n+1
以下、k は 1 でないとする。
(i) ある k で a[k] == n+1 とするとすべての i に対して a[i] == k。
  これは a[k] == n+1 に反する(k < n だから)のでダメ。
  よってすべての k に対して a[k] <= n。
(ii) ある k で a[k] == n とすると k 以外の i に対して a[i] == k。
  (ただし k が 0 の場合は a[i] = 10(n進))
  ここで 0 でも 1 でも k でもなく a[1](最大2桁)にも出てこない数字 j
  (n >= 7 なのでそういう数字は存在する)を考えると j は「□個」の部分には
  現れず「□が」の部分に1回だけ現れるので a[j] = 1。
  これは a[j] == k に反する(k != 1 だった)のでダメ。
よってすべての k に対して a[k] < n (つまり1桁)

■(C) a[1] == 11(n進)== n+1 のときは
「この文は0が1個, 1が11個, 2が2個, 3が1個, 4が1個, ...,
  (n-1)が1個あります。」

a[1](== 11(n進))には 0 が現れず a[i] (i != 1) にも (B) により 0 は現れない
(「0個」はありえない)から「この文は0が1個, 1が11個,」まで確定。
ここまで 1 は4回現れたので残り n-3回。
ということは1箇所だけ「1個」ではなく(これが「kは□個」だったとする)それ以外は
すべて「1個」のはず。
k の出現が1回にならないようにするには「kは□個」の「□」の場所に k が現れるしかない。
つまり「kはk個」。このつじづまがあうのは k == 2 の場合だけ。

■(D) a[1] == 10(n進)== n はありえない。
a[1](== 10(n進))には 0 が1回現れ a[i] (i != 1) には (B) により 0 は現れない
(「0個」はありえない)から「この文は0が2個, 1が10個,」まで確定。
ここまで 1 は2回現れたので残り n-2回。
ということは残り(2の回数も含む)はすべて「1個」。しかし 2 が「2は」と「0は2個」
の2回すでに現れているのでダメ。

(A)(C)(D) から
■(E) 「この文は0が1個, 1が11個, 2が2個, 3が1個, 4が1個, ..., (n-1)が1個あります。」
のケース以外のときはすべての i に対して a[i] < n(つまり1桁)。さらに「0は1個」

「0個」はありえないので 0 が現れるのは「0は」の1箇所だけ。

以降はすべて1桁のケースのみを考える。

■(F) Σi*(a[i]-1) = 数字の総出現回数 == 2*n

すべて1桁なので。

ここからが本番。
■(G) 4 から上で「1個」以外が現れるのは最大1箇所。

もし2箇所以上で「1個」ではなかったとすると x = min {i | i >= 4, a[i] > 1} と
それとは別の場所の y(> x >= 4)で a[x] > 1, a[y] > 1。
a[x] > 1 なので「xは」以外に「x個」がどこかに現れる。
「0は1個」なので「0はx個」はありえない。
仮に「1はx個」として現れたときは「y個」の方をどこに現れるか考える。
以下の考察は x,y の大小は関係せず対称なので「1は□個」に現れない方を x とする。
よって「x個」は 2 より上の場所に現れる。
例えば「2はx個」として現れた場合を考える。x >= 4 なので 2 は4回以上現れる
「2は」で1回現れているのを除外すると「2個」として3回以上現れる。
「0は2個」と「1は2個」はありない。
(0 に関しては「0は1個」から。1 に関しては「1個」の数が少ないと (F) の左辺が
n^2オーダになるため)
もし「3は2個」でなかったら 4 より上に「2個」が3回以上現れる。
「3は2個」の場合「2個」の登場が一つ減るが今度は「3個」の登場(「1は3個」もありない)
を加味して、合わせて 4 より上に「2個」または「3個」が3回以上現れる。
どちらにしても 4 より上に「1個」でないのが3回以上現れる。
すでに x,yとして2箇所を除外しても別の場所 z に「1個」でないのが現れる。
z > x >= 4 なので z >= 5。
先ほどの x,y の代わりに y,z(>= 5) で考えると 4 より上に「1個」でないのが
(今度は)4回以上現れる。
すでに x,y,zとして3箇所を除外しても別の場所 w に「1個」でないのが現れる。
w > z >= 5 なので w >= 6。
ということを無限に繰り返せることになるが有限なので無理。

■(H) 4 から上がすべて「1個」はありえない。

4 から上がすべて「1個」とする。
1 の出現回数は 4 から上がすべて「1個」の (n-4)回と「0は1個」の1回と
「1は」の1回を合わせて最低 (n-2)回。
1 の出現回数を m とすると m >= n-2 > 4 なので m の出現回数は1個。
これは「mは」と「1はm個」の最低2個現れることに反する。

■(I) 4 から上に「1個」以外が1箇所だけ現れるのは
「この文は0が1個, 1が5個, 2が3個, 3が2個, 4が1個, ...,
(n-4)が1個, (n-3)が2個, (n-2)が1個, (n-1)が1個あります。」

1 の出現回数は 4 から上で一つだけ例外がある「1個」の (n-5)回と「0は1個」の1回と
「1は」の1回を合わせて最低 (n-3)回。
1 の出現回数を m とすると m >= n-3。
「2がm個」や「3がm個」はありえない(2 や 3 が m個現れる余裕はない)ので
m の出現は「1がm個」と「mが」の2箇所だけで「mが2個」。
よって 2 の出現回数は2回以上になるが「2が2個」ではさらに 2 がまた出てきて
数が合わなくなるので可能性があるのは「2が3個」。
「この文は0が1個, 1がm個, 2が3個, 3が2個, 4が1個, ...,
(n-4)が1個, (n-3)が2個, (n-2)が1個, (n-1)が1個あります。」
で 1 以外のつじつまは合うので最後に 1 の数を合わせて「1が(n-3)個」。

以上。

あー、自己参照問題はややこしいー。
(G) はもしかしたら対角線論法とか使ってもっと簡単に証明できるかもしれない。
(誰かお願い)
示そうとしてたんですが、先を越されました。
方針はだいたい同じで (F) までは出たんですが (G) ができなくて……
丁寧に場合分けしていけばよかったんですね。

ところで (G) の別証明を考えていたら雰囲気の違う方法が見つかりました。
a[1] >= 4 は仮定して、二桁の値を含まない解が一意なことを示します。

天下り式ですがとりあえず定義。
b[i] := (i-2)*(a[i]-1)
S := b[3] + b[4] + ... + b[n-1]
S' := S - b[a[1]]

■(F') S = -b[1] = a[1] - 1

Σi*(a[i]-1) = Σa[i] = 2*n から少し計算すると Σb[i] = 0 が出る。
b[0]=b[2]=0 なので b[1] を移項すれば求める式が出る。

■(J) a[a[1]] = 2, S' = 1

(F') に S = S' + b[a[1]] を入れて計算すると S' + (a[1]-2)*(a[a[1]]-2) = 1
a[1] は「1がa[1]個」に現れるので a[a[1]]>=2 であり、また a[1]-2 >= 2 だから
左辺第二項は 0 または 2 以上。さらに S' も非負だから主張が従う。

■(K) i>3, i!=a[1] のとき a[i]=1 であり、また a[3]=2

(J) より S' = 1 となるが、 i>3 なら b[i] は 0 または 2 以上。
したがって b[3]=1, それ以外の i では b[i]=0 でなければならない。

これで a[1], a[2] 以外は確定します。あとは難しくないでしょう。
エレガント!
Σ(ほとんど非負)== 0 を作るのが見事ですね。

Σ(i-2)*(a[i]-1) の式に意味的背景はあるのでしょうか。

やっぱり「どこから出てきたの?」って気はしますよね。意味はよく分かりませんが、なんとなく a[1] の大きさを評価すればいいんじゃないかと思って

a[1] = なんかa[1]を含まない式

みたいにできないかなと思っていたら気が付きました。

全探索バージョン。
直前のお題"自然数の分割"のプログラムを応用して
「数字の数は2n~2n+2 (2進は例外)」という条件で探索範囲を限定。
2進から16進まで探索してCore2Duo 1.2GHzで 29秒

2進:
この文は0が11個,1が100個あります。
3進:
この文は0が10個,1が10個,2が2個あります。
この文は0が1個,1が11個,2が2個あります。
この文は0が2個,1が2個,2が10個あります。
...
10進:
この文は0が1個,1が11個,2が2個,3が1個,4が1個,5が1個,6が1個,7が1個,8が1個,9が1個あります。
この文は0が1個,1が7個,2が3個,3が2個,4が1個,5が1個,6が1個,7が2個,8が1個,9が1個あります。
...
16進:
この文は0が1個,1が11個,2が2個,3が1個,4が1個,5が1個,6が1個,7が1個,8が1個,9が1個,aが1個,bが1個,cが1個,dが1個,eが1個,fが1個あります。
この文は0が1個,1がd個,2が3個,3が2個,4が1個,5が1個,6が1個,7が1個,8が1個,9が1個,aが1個,bが1個,cが1個,dが2個,eが1個,fが1個あります。
28.799607992172sec
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
function cmp(&$a1,&$a2)
{
    foreach($a2 as $k=>$v)
        if($a1[ord(base_convert($k,10,36))]!=$v)
            return false;
    return true;
}
function judge($a,$n)
{
    $b=array();
    for($i=0;$i<$n;++$i)
    {    $c=base_convert($i,10,$n);
        $b[]="${c}が".base_convert($a[$i],10,$n)."個";
    }
    $str=implode(",",$b);
    $a1=count_chars($str);
    if(cmp($a1,$a))
        echo "この文は${str}あります。\n";
}

function part($a,$n,$c,$s1,$s2,$m)
{
    if(!$c)
    {    $m1=min($m-$s1,$s2+1);
        for($i=$n*2-$s1;$i<=$m1;++$i)
        {    $a[$c]=$i;
            judge($a,$n);
        }
    }
    else
    {    $m1=min($m-$s1,$s2/$c+1);
        for($i=1;$i<=$m1;++$i)
        {    $a[$c]=$i;
            part($a,$n,$c-1,$s1+$i,$s2-$c*($i-1),$m);
        }
    }
}

$t=microtime(true);

for($n=2;$n<=16;++$n)
{    echo "${n}進:\n";
    $m=max($n*2+2,10);
    part(array(),$n,$n-1,0,$m,$m);
}

echo microtime(true)-$t,"sec\n";
?>
数字の出現回数の合計と(数字x(出現回数-1))の積分の関係で縛りを追加したら
#4376と同条件で4.1秒ほどになりました。

リストは変更していないcmp()とjudge()省略。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
function part($a,$n,$c,$s1,$s2,$m)
{
    if(!$c)
    {    for($i=$s2-$s1;$i<=min($m-$s1,$m-$s2+1);$i+=$n-1)
            if($i>=$n*2-$s1)
            {    $a[$c]=$i;
                judge($a,$n);
            }
    }
    else
    {    $m1=min($m-$s1,($m-$s2)/$c+1);
        for($i=1;$i<=$m1;++$i)
        {    $a[$c]=$i;
            part($a,$n,$c-1,$s1+$i,$s2+$c*($i-1),$m);
        }
    }
}

$t=microtime(true);

for($n=2;$n<=16;++$n)
{    echo "${n}進:\n";
    $m=max($n*2+2,10);
    part(array(),$n,$n-1,0,0,$m);
}

echo microtime(true)-$t,"sec\n";
?>
#4373 を参考にリミットを設定して、全てのパターンを並行処理するようにしたものです。
ただし、うちの環境 (Mac OS X 10.5 PPC64) ではリソースの問題で radix が 5 までしか計算できませんでした。

% time ./quiz 5
この文は0が1個, 1が3個, 2が2個, 3が3個, 4が1個あります。
./quiz 5  1.66s user 2.02s system 86% cpu 4.269 total

おそらくパターン数が爆発的に増える関係で、全てのシステムスレッドがブロックしてしまい、先に進まなくなるんじゃないかと思います。
この方式は Erlang じゃないと無理かもしれないです。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
let limit radix =
   match radix with
   | 2 -> 8
   | n -> n + 2

let char_table =
   Array.map begin fun i ->
      Printf.sprintf "%x" i
   end (Array.init 16 (fun i -> i))

let lstring_of_radix_num radix num =
   let rec loop n acc =
      if n < radix then
         char_table.(n) :: acc
      else
         let rem = n mod radix in
         loop ((n - rem) / radix) (char_table.(rem) :: acc)
   in
   loop num []

let string_of_lstring str =
   List.fold_left begin fun acc ch ->
      acc ^ ch
   end "" str

let string_of_radix_num radix num =
   string_of_lstring (lstring_of_radix_num radix num)

let slist_of_alist radix alist =
   List.map begin fun i ->
      lstring_of_radix_num radix i
   end (List.flatten alist)

let count_digits radix str_list num =
   match lstring_of_radix_num radix num with
   | [ch] ->
        List.fold_left begin fun sum ch' ->
           sum + (if ch = ch' then 1 else 0)
        end 0 (List.flatten str_list)
   | _ -> failwith "unknown"

let validate radix alist =
   let str = List.flatten (slist_of_alist radix alist) in
   List.for_all begin fun pair ->
      match pair with
      | [i; num] -> begin
           match lstring_of_radix_num radix i with
           | [ch] ->
                num =
                   List.fold_left begin fun sum ch' ->
                      sum + (if ch = ch' then 1 else 0)
                   end 0 str
           | _ -> failwith "unknown"
        end
      | _ -> failwith "unknown"
   end alist

let search radix =
   let lim = limit radix in
   def search'(num, start, acc) =
      def result(alist) & wait() = reply alist to wait in
      def search''(n) =
         let acc' = [num; n] :: acc in
         match num - 1 with
         | next when next < 0 ->
              if validate radix acc'
              then result(acc')
              else 0
         | next ->
              let start' =
                 (count_digits radix (slist_of_alist radix acc') next) + 1
              in
              if start > lim then failwith "unknown";
              result(search'(next, start', acc'))
      in
      for i = start to lim do
         spawn search''(i)
      done;
      reply wait() to search'
   in
   search'(radix - 1, 1, [])

let print_result radix alist =
   let str =
      List.fold_left begin fun acc pair ->
         match List.map (string_of_radix_num radix) pair with
         | [i; n] ->
              acc ^ i ^ "が" ^ n ^ "個" ^ ", "
         | _ -> failwith "unknown"
      end "この文は" alist
   in
   let str' = (String.sub str 0 ((String.length str) - 2)) ^ "あります。" in
   print_endline str'

let main () =
   match Sys.argv with
   | [|_; radix |] ->
        let radix' = int_of_string radix in
        let result = search(radix') in
        print_result radix' result
   | _ -> print_endline "usage: command radix"

let () = if not !Sys.interactive then main ()
#4381 を、ちゃんと動くように修正しました。
その分、おもしろみは減った気がしますが。
結局、この問題の場合、一つ一つの処理はあまり重くないので、プロセスをなるべく生成しない方が速いみたいですね。
jocamlopt でコンパイルしたもので、
 
% time ./quiz 16
この文は0が1個, 1が11個, 2が2個, 3が1個, 4が1個, 5が1個, 6が1個, 7が1個, 8が1個, 9が1個, aが1個, bが1個, cが1個, dが1個, eが1個, fが1個あります。
^C
./quiz 16  1.48s user 0.04s system 63% cpu 2.369 total

こんな感じでした。

答を全て表示しようとするように変更したので、止まらなくなりました。
上記の実行例もキーボード割り込みで止めています。
一応、第二引数に何か入れると、前回と同じように一つ目を見付けた時点で終了します。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
let limit radix =
   match radix with
   | 2 -> 8
   | n -> n + 2

let char_table =
   Array.map begin fun i ->
      Printf.sprintf "%x" i
   end (Array.init 16 (fun i -> i))

let lstring_of_radix_num radix num =
   let rec loop n acc =
      if n < radix then
         char_table.(n) :: acc
      else
         let rem = n mod radix in
         loop ((n - rem) / radix) (char_table.(rem) :: acc)
   in
   loop num []

let string_of_lstring str =
   List.fold_left begin fun acc ch ->
      acc ^ ch
   end "" str

let string_of_radix_num radix num =
   string_of_lstring (lstring_of_radix_num radix num)

let slist_of_alist radix alist =
   List.map begin fun i ->
      lstring_of_radix_num radix i
   end (List.flatten alist)

let count_digits radix str_list num =
   match lstring_of_radix_num radix num with
   | [ch] ->
        List.fold_left begin fun sum ch' ->
           sum + (if ch = ch' then 1 else 0)
        end 0 (List.flatten str_list)
   | _ -> failwith "unknown"

let validate radix alist =
   let str = List.flatten (slist_of_alist radix alist) in
   List.for_all begin fun pair ->
      match pair with
      | [i; num] -> begin
           match lstring_of_radix_num radix i with
           | [ch] ->
                num =
                   List.fold_left begin fun sum ch' ->
                      sum + (if ch = ch' then 1 else 0)
                   end 0 str
           | _ -> failwith "unknown"
        end
      | _ -> failwith "unknown"
   end alist

def result_post(alist) & result_get() = reply alist to result_get

def search(radix) =
   let lim = limit radix in
   let rec search' label num acc =
      if label < 0 then
         (if validate radix acc then spawn result_post(acc))
      else
         let acc' = [label; num] :: acc in
         for i = 1 to lim do
            search' (label - 1) i acc'
         done
   in
   for i = 1 to lim do
      search' (radix - 1) i []
   done;
   0

let print_result radix alist =
   let str =
      List.fold_left begin fun acc pair ->
         match List.map (string_of_radix_num radix) pair with
         | [i; n] ->
              acc ^ i ^ "が" ^