0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CodeIq:「デジタル・ルート」問題の解答

Last updated at Posted at 2025-02-01

Kawazoe(@riverplus)さんのCodeIQの問題です。
問題はこちらの御世話になりました。解答例もあります。
->https://blog.goo.ne.jp/r-de-r/e/bad4f9b4a5c4fecd67db04fe31c51c78
こちらにも情報があります。
->https://togetter.com/li/1040869

同じ数字を含む数は同じF値となるので、
組合せを網羅する解き方はすぐ浮かびます。
場合の数が大きくなりそうなので、他の方法をいろいろ考えましたが、
その時はうまくいかず、結局組み合わせを利用しました。
数字の組み合わせのみ直接求めて、その組み合わせの並び方の数は計算しました。
そしてすべての組み合わせに対して、
その組み合わせのF()に並び方の数を乗じました。

00..0~99..9(k桁)のすべての数と、0..9から重複を許してk個並べる場合の数が
一対一に対応しますので、k個の組み合わせごとに並び方の数を計算します。
ある組み合わせの数の和を取りそれに上位桁の数字の和を足すと
各桁の和が求まります。

例によって、abcd..tuというn桁の数なら、
nけた目の0~a-1とn-1けた目の000..0~999..9から
Σ(0<=x<=a*10^n-1)F(x)を計算し、
aとn-1桁目の0~b-1とn-2桁の00..0~99..9から
Σ(a*10^n<=x<=a*10^n+b*10^(n-1)-1)F(x)を計算し
a+bとn-2桁目の0~c-1とn-3桁の0..0~9..9からΣ(a*10^n+b*10^(n-1)<=x<a*10^n+b*10^(n-1)+c*10^(n-2)-1)F(x)
を計算し...というように小さい桁に移動していきます。

collect/5で0~10^i-1の組み合わせを求め、上位桁の情報とともにf/3に送り、
f/3ではその組み合わせの並べ方の数を求めるとともに、
その組み合わせの最初の合算を行い、sums/1に送ってF()を計算し、
F()に並べ方の数を乗じます。
メインですべての結果を合計します。
最後はa+b+c+..+tと0..uからF()を計算して足します。
nけたの00..00~00..09の和は1と計算されますが実際は0なので補正します。

#julia ver 1.41,1.53,1.10.3

function solve(n0,a)  
    function ntor(k)
        u = Int(floor(log10(k)))
        ar = []
        x = k
        for i in 1 : u+1
            (x,y) = divrem(x,10)
            push!(ar,y)
        end
        return ar
    end
    function ncr(k,h)            
        mul = 1
        for i in 1 : h
            mul = div(mul*k,i)
            k -= 1
        end
        return mul
    end
    function sums(n)    #ある組み合わせのF(),最初の計算は済んでいる
        if n < 10
            return 1   #f/3での計算分 
        else
            sum = 0
            while n > 0
                (n,r) = divrem(n,10)
                sum += r
            end
            return 1 + sums(sum)  #1はf/3での計算分
        end
    end
    function f(a,b,ar) #ある組み合わせのF()の総計を求める
        ssum=0
        for i in 1:10
            ssum += ar[i] * (i-1)  #ある組み合わせの最初の和
        end
        com = 1 #ある組み合わせの並び替えの数
        k = length(ar0)
        for i in 1 : length(ar)
            com *= ncr(k,ar[i])
            k -= ar[i]
        end
        ret = 0
        for i in 0 : b
            ret += sums(a+i+ssum) * com 
        end
        return ret
    end
  function collect(n,m,a,b,ar) #組み合わせを作ってf/3に送る
    if m > 9 return 0   
    elseif n == 0
        return f(a,b,ar)  #ar->0~9それぞれの数->0~99..99に対応
    else
        sum = 0
        ar1 = copy(ar)
        ar1[m+1] += 1
        sum += collect(n-1,m,a,b,ar1) + collect(n,m+1,a,b,ar)
        return sum
    end
  end
  msum = 0
  if n0 > 9
    ar0 = ntor(n0)
    x,y = 0,0
    for i in length(ar0)-1 : -1 : 1
      x = pop!(ar0)
      if x != 0
       msum += collect(i,0,y,x-1,zeros(Int,10)) #上位桁と0~x*10^(i-1)-1
        y += x   #iより上位の桁の数の和
      end
    end
    for i in 0 : ar0[1]
      msum += sums(y+i)   #1のけた
    end
    msum -= 10    #最初の000..00~00..09を補正
  end
  s = a == msum ? "ok " : "no "
  println(s,n0,"->",msum)
 end

solve(9, 0)
solve(10, 1)
solve(20, 12)
solve(48, 48)
solve(100, 136)
solve(149, 200)
solve(5432, 10539)
solve(9876, 19951) 
solve(54321, 113023)
solve(90817263, 207841726)
solve(99003157, 227032799)

Prologで書くにあたり、もっと簡便で早そうな方法がないか考えていた時、
和が高々二桁(9と入力値の桁数の積)なのに気づき、和で分類することにしました。
一桁位大きい数を扱えます。
ar[k,i+1]にk桁以下の各桁の和がiになる場合の数を入れます。(配列は1から)
k-1桁目までの和がxでa(0<=a<=9)を加えてy=x+aになったとすると
ar[k,y+1]+=ar[k-1,x+1] 
一桁目の数字はar[1,_]に入れます。
Prologではput/5でリストに入れて行います。

fun/2でput/5を呼び出しています。
NをM+1桁の数としますとdivmod(N,10^M,D,Y)なら
0~D*10^M-1とD*10^M~D*10^M+Yに分け
再帰的に0~D-1とfun(10^M-1,R1)及びDとfun(Y,R2)実行し、
その中でput/5を呼びリストを更新します。
fn/2でF(N)を、gn/4でG(N)を計算します。
最初に合算するときが回数に入っていませんので
2桁以上の時は最後にn0-9を足します。

%SWI-Prolog ver 7.42,7.64,9.25
%:-initialization(start).       %Online Prolog Compiler,ideone.    
%start.

for(-1,_,R,R):-!. %0~Y-1までloopを繰り返し
for(Y,AR1,AR0,R):-put(1,Y,AR1,AR0,AR2),Y1 is Y-1,for(Y1,AR1,AR2,R).

put(K,_,AR1,R,R):-
  K1 is K-1,length(AR1,K1),!.
put(K,J,AR1,R1,R):-  
  K1 is J+K,nth1(K,AR1,X),nth1(K1,R1,Y,AR2),Z is X+Y,
  nth1(K1,R2,Z,AR2),K2 is K+1,put(K2,J,AR1,R2,R).

cal(0,R,R):-!.   %各桁の和
cal(N,R1,R):-divmod(N,10,N1,M),R2 is R1+M,cal(N1,R2,R).
 
fn(N,0):-N<10,!.  %F(n)
fn(N,R):-cal(N,0,R1),fn(R1,R2),R is 1+R2.

fun(N,AR):-
  N<10,!,N1 is N+1,length(L1,N1),maplist(=(1),L1),N2 is 10-N1,
  length(L2,N2),maplist(=(0),L2),append(L1,L2,AR).
fun(N,AR):-
  M is floor(log10(N)),K0 is (M+1)*9+1,length(AR0,K0),maplist(=(0),AR0),    %AR0=zeros(K0)
  X is 10^M,X1 is X-1,divmod(N,X,Y,RE),fun(X1,AR1),fun(RE,AR2),
  Y1 is Y-1,for(Y1,AR1,AR0,AR3),put(1,Y,AR2,AR3,AR).

gn(N,_,R,R):-
  N<11,!.     % G(n)
gn(N,AR,R1,R):-
  N1 is N-1,fn(N1,X),nth1(N,AR,Y),R2 is R1+X*Y,N2 is N-1,gn(N2,AR,R2,R).

solve(N,R):-
  fun(N,AR),length(AR,M),gn(M,AR,0,R1),(N>9->R is R1+N-9;R = R1).

start:-
  f(N,A),(solve(N,R)->(R==A->Str=" ok  ";Str=" no  ");
  write(fail)),write(" "),write(Str),writeln(N->R),fail.
start.


f(7,0).
f(9, 0).
f(10, 1).
f(20, 12).
f(48, 48).
f(100, 136).
f(149, 200).
f(5432, 10539).
f(9876, 19951).
f(54321, 113023).
f(90817263, 207841726).
f(99003157, 227032799).

最後に上の方法で再帰でなく下の位から実行するようにしたコードです。
断然早いです。

function solve703(n0,a)
    function ntor(k)
      ar = []
      x = k
      while x > 0
          (x,y) = divrem(x,10)
          push!(ar,y)
      end
      return ar
  end
  function fn(x)
    if x < 10
      return 0
    else
      sum = 0
      while x > 0
        (x,r) = divrem(x,10)
        sum += r
      end
      return 1 + fn(sum)
    end
  end
  if n0 < 10
    sum = 0
  else
    ar0 = ntor(n0)    #入力値を数列化
    m = length(ar0)
    k0 = (m+1)*9+1
    ar1 = zeros(Int,m,k0,2)  
    ar2 = zeros(Int,m,k0,2)
    for i in 1:10
      ar1[1,i,1] = 1
    end
    for i in 1 : ar0[1] + 1
      ar2[1,i,1] = 1
    end
    for i in 2 : m-1
      for j0 in 0 : k0-10
          j = j0 + 1
        for k in 0 : 9            
           x = j0+k
             ar1[i,x+1,1] += ar1[i-1,j,1]  #1~iけた目の0~9すべてを含む
        end
        for k in 0 : ar0[i] - 1 #i桁目をaとすると0~a-1と0~i-1桁の0~9すべて
            x = j0+k
            ar2[i,x+1,1] += ar1[i-1,j,1]
        end
        x = j0 + ar0[i]
          ar2[i,x+1,1] += ar2[i-1,j,1]  #i桁目のaとi-1桁目のar2
       end
    end
    sum=0
    for j0 in 0 : k0 - 10
      j = j0 + 1
      for k in 0 : ar0[m] - 1
           x = j0 + k
           ar1[m,x+1,1] += ar1[m-1,j,1]
      end
      x = ar0[m] + j0
      ar2[m,x+1,1] += ar2[m-1,j,1]
    end
    for i in 9 : k0-1
      sum += fn(i) * (ar1[m,i+1,1] + ar2[m,i+1,1])
    end
    sum += (n0 - 9)
  end
  s = a == sum ? "ok " : "no "
  println(s,n0,"->",sum)
 end

追記
 最後のコードが断然速いと書いて投稿したのですが、
togetter.comを見に行ったら、まさに桁違いの速さのコードが載っていました。
 私は他人のコードを読めませんし、コメントも理解できていないのですが、
 よろしかったら見に行かれてください。
 

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?