質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

6回答

2467閲覧

「限られた数字で作れる数の、まんなかの値」を求めたい。

s8079

総合スコア36

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2016/11/19 08:00

編集2016/11/19 08:44

###前提・実現したいこと
「限られた数字で作れる数の、まんなかの値」を求めたいです。
例えば,
0, 4, 5以外の数を使わずに作ることができる3桁以下の非負の整数は,
0, 4, 5, 40, 44, 45, 50, 54, 55, 400, 404, 405, 440, 444, 445, 450, 454, 455, 500, 504, 505, 540, 544, 545, 550, 554, 555
の27個です。
中央は14番目の数なので、求める値は444になります。
入力は,
3 045(桁数␣使える数)
という感じです。
出力は,
444
のように,中央の値を出力します。
作れる数が偶数個の場合には、中央に近い数を2つ,小さい順にコンマ区切りで並べます。
なお,桁数の上限の最小値は1。最大値は15です。
不正な入力に対処する必要はありません。

入力出力
15 12345789444444444444444,444444444444445
15 123456789494949494949495
15 0123456789499999999999999,500000000000000

ソースコードを提示していただければ自分で解読します。
アルゴリズムのヒントでもいいので教えていただけると幸いです。

###発生している問題・エラーメッセージ
処理に時間がかかりすぎて終わりません。

###該当のソースコード

C++

1#include <iostream> 2#include <string> 3#include <vector> 4void median(std::vector<std::string> *str, const std::string &number, const int &digit, const std::string &buffer = "", const bool &flag = false) { 5 if (digit > 0) { 6 if (flag == false && digit > 1 && number.front() > '0') { 7 median(str, number, digit - 1, buffer + '0'); 8 } 9 for (const char c : number) { 10 median(str, number, digit - 1, buffer + c, true); 11 } 12 } 13 else if (digit == 0) { 14 str->push_back(buffer); 15 } 16 return; 17} 18int main(void) { 19 int digit; 20 std::string number; 21 while (std::cin >> digit >> number) { 22 std::vector<std::string> str; 23 //m:組み合わせの数 24 /* 25 long long int m = 0; 26 if (number.front() > '0') { 27 for (int i = 1; i < digit; ++i) { 28 m += pow(number.size(), i); 29 } 30 } 31 m += pow(number.size(), digit); 32 */ 33 median(&str, number, digit); 34 if (str.size() % 2 == 0) { 35 std::vector<std::string>::const_iterator itr = str.cbegin(); 36 std::advance(itr, str.size() / 2 - 1); 37 std::cout << *itr << "," << *(itr + 1) << std::endl; 38 } 39 else { 40 std::cout << str.at(str.size() / 2) << std::endl; 41 } 42 } 43 return EXIT_SUCCESS; 44}

###試したこと
数を文字列として扱い,組み合わせをすべて探索して,そこからまんなかの値を求めました。
ソースコード中にあるように組み合わせの数は求めることができます。
この問題は自分も最初勘違いしていましたが,使用できる数に「0」がなくても空白は使用できるようです。

###補足情報(言語/FW/ツール等のバージョンなど)
言語:C++11

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MasahikoHirata

2016/11/19 08:09

入力の意味を理解するまで時間がかかった。’桁数’+’空白’+’使える数字’と説明があれば理解しやすいですね。
matsu

2016/11/19 08:18

入力の最初の数字は桁数、2つ目はどの数字を使うか、ということで良いですかね
MasahikoHirata

2016/11/19 08:29

matsuさん、質問者が問題を修正され、その通りの出題です。
guest

回答6

0

ベストアンサー

アルゴリズムのプロトタイピングにPython言語を使ってみました。基本的な考え方は次の通りです。

  • 「使う数字の個数」を基数(raidx)とみなす。つまりn桁のradix進数として中央値を考える。
  • 「使う数字」に0が含まれない場合、長さがn桁未満の組合せ総数をカウントし(offset)中央値計算時のオフセットとする。
  • 「使う数字」に0が含まれる場合、n桁のradix進数数え上げにて既にn桁未満の数を含むため、オフセットを調整する。(入力が3 045のとき、総数radix ** nには004, 005, ...が計上されている)

Python

1n = 15 2a = list("12345789") 3 4# 10進数値valueを"使う数値からなるradix進数"で再構築 5def dump(value): 6 result = "" 7 while 0 < value: 8 result = a[value % radix] + result 9 value = value // radix 10 print(result) 11 12radix = len(a) # 基数 13 14offset = 0 15if not '0' in a: 16 # offset = Σ_{i=0}^{i < n} (radix ^ i) 17 offset = sum([radix ** i for i in range(n)]) 18else: 19 offset = 1 20 21# 中央値を計算(10進数) 22median = (radix ** n - offset) // 2 23 24if radix % 2 == 0: 25 dump(median) 26 dump(median + 1) 27else: 28 dump(median)

投稿2016/11/19 09:04

編集2016/11/19 09:18
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yohhoy

2016/11/19 09:27

余談ですが「使用できる数に「0」がなくても空白は使用できる」は私も騙されました。というか競技プログラミング(?)的には暗黙に読み取るべき仕様なんすねコレ...
raccy

2016/11/19 09:31

入力と出力の例が3つあるので競プロ勢ならそこら辺から読み取るもんなのかなと思ってます。答えがどうしても合わなくて、私も最初悩みまくってしまいましたが…。
MasahikoHirata

2016/11/19 09:45

さすが、C++でコーディングしている間に、見やすい答え。
s8079

2016/11/19 10:48

Pythonは初めてなので少し説明がほしいのですが,dump関数の中はどうなっているのでしょうか。
yohhoy

2016/11/24 06:50 編集

dump関数では 10進数→radix進数変換 を行なっています。Python版をC++に置き換えると概ね次のような感じです(コメント内でインデント表現のため全角空白を使っています): std::vector<char> a = { 使う数値を文字単位で格納 }; size_t radix = a.size(); void dump(uint64_t value) {  std::string result;  while (0 < value) {   result = a[value % radix] + result;   value = value / radix;  }  std::cout << result; } 追記:たしかにPythonの「//」演算子は説明が要りますね... 整数同士の切り捨て除算(例: 5/2 == 2)を行いますから、C++だと普通に整数型同士の除算「/」に置き換えられます。
s8079

2016/11/19 11:09

Pythonでは「/」も「//」も除算なんですね。 解決しました。
guest

0

ソースを書いている内に、考え方はyohhoyさんが書いてしまったので、ソースだけです。Rubyですけど。

Ruby

1n_s, list_s = gets.split 2n = n_s.to_i 3list = list_s.chars.sort 4base = list.size 5max_n = base**n 6pattern_num = max_n 7(1...n).each { |x| pattern_num += base**x } unless list.include?('0') 8middle_list = [max_n - pattern_num / 2 - 1] 9middle_list << middle_list[0] + 1 if pattern_num.even? 10puts(middle_list.map do |num| 11 num.to_s(base).chars.map(&:to_i).map { |i| list[i] }.inject(&:+) 12end.join(','))

実は0を含んだ方が簡単です。なぜなら、そのままn桁のbase進数と見なせてしまうので。"000...0001"は"1"に集約されるので、それぞれの桁を数える必要は無くなります。

投稿2016/11/19 09:24

編集2016/11/19 09:51
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

メモ的に考えをまとめてみます。


問題点:

  • 中央値を正しく計算できていない(これは必ず15ケタ以下になるはずのため)

方向性:

  • 該当のリストが総数いくつになるか計算し半分の数を出す
  • 上記番目の数字を出力する

課題:

  • リストの数は[使える数の数]乗の[桁数]になる
  • 使える数が奇数の場合、使える数の中央値を連続したものがリストの中央値になる?
  • 使える数が偶数の場合は最大値を半分にした数が中央値になる?

投稿2016/11/19 09:17

matsu

総合スコア702

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

raccy

2016/11/19 09:35

問題点は「処理に時間がかかりすぎて終わりません。」です。質問にも明記されています。
matsu

2016/11/19 09:42

すみません。そこ、端折りました。出力結果を見た際、指定桁数以上の値がでているようだったので必要以上の計算をしているであろうと判断しました。 ですが、. ではなく , が使われていることに今気付きました。
guest

0

とりあえず0を考えない場合の基本的な考え方だけ提案します。
桁数 = K, 使える数の種類 = N とします。

各桁の組み合わせの数
1桁 : N
2桁 : N^2
:
K桁 : N^K
総数 : N + N^2 + ...+ N^K となります。
よって、求める中間値が何桁目(k)の先頭から何番目(x)に位置するかは簡単に計算できます。
あとはk桁目の組み合わせを辞書順にx番目まで走査(列挙)すればよいです。
また、この走査も、k桁目の組み合わせの総数は N * N^(k-1) という法則(部分解)を利用すれば
実際の列挙(数字の作成)もかなり端折ることができます。

おそらく0を含む場合の各桁の組み合わせの数も簡単な式で表せると予想しています。

投稿2016/11/19 08:50

編集2016/11/19 08:59
can110

総合スコア38256

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

s8079

2016/11/19 09:09

確かにx番目までの走査で良いです。 しかし,この問題は組み合わせの最大が1000兆通りになります。 したがって,x番目までの走査でも500兆通り試さなければなりません。
can110

2016/11/19 09:18

編集した回答に追記したのですが、部分解を使えば実際の走査もかなり端折る(ジャンプする)ことができます。 例えば12345の3桁の組み合わせ からx番目までジャンプしたい場合 "1"始まりの総数 (=2桁の組み合わせの総数)=sum1を超える?→とばす +"2"始まりの総数(=2桁の組み合わせの総数)=sum2 超える?→超えない この位置から2桁の組み合わせ総数のx-sum1+sum2番目を探す といった感じです。
can110

2016/11/19 09:20

0を含む場合は、0が左端にくる場合のみを除けばよいので 1桁 : (N-1) 2桁 : (N-1) * N : K桁 : (N-1) * N^(K-1) 総数 : (N-1) + (N-1) * N + ...+ (N-1) * N^(K-1) でよさそうですね。
can110

2016/11/19 09:31

スマートな回答出ましたね。 こんな考えでもできるという提案までで閉めます。
guest

0

考え方をまとめてみる。
入力は桁数、使える数字である。
使える数字に’0’があった場合、頭が’0’のものは’0’だけである。(解の1つ)
後は桁数になる数字の組み合わせをカウントして、その組み合わせ数が前述の’0’が選択できる数字にある場合は+1して偶数か奇数かを判断し、それを算出する。

15桁までという事を考えてlong long intで処理してみる。

C++

1#include <iostream> 2#include <string> 3#include <vector> 4unsigned char used_number[10];   // 使用数字 0=not used,1=used 5 6long long int check_number(int digit) 7{ 8 long long int work; 9 if( digit == 1 ) 10 { 11 for(int i = 0,work = 0LL;i <10;i++) 12 if(used_number[i] != 0) 13 work++; 14 return work; 15 } 16 for(int i = 0;i<10;i++) 17 work += check_number(digit-1); 18  if(used_number[0] == 1) // 頭が’0’場合、その数を引く 19 work -= number_of_count; 20 return work; 21} 22long long int calc_number(int digit,long long int current_pos,long long int result_pos) 23{ 24 long long work; 25 if( digit == 1) 26 { 27 for(int i = 0,work = 0LL;i<10;i++ ) 28 { 29 if(used_number[i] != 0 ) 30 { 31 if(work == result_pos ) 32 { 33 cout << used_number[i] ; 34 return; 35 } 36 } 37 } 38 return work; 39 } 40 for(int i = 0;i<10;i++) 41 { 42 current_pos += calc_number(digit-1,current_pos,result_pos); 43 } 44  if(used_number[0] == 1) // 頭が’0’場合、その数を引く 45 current_pos -= number_of_count; 46 return current_pos; 47} 48 49int main(void) { 50 int digit; 51 std::string number; 52  long long int work; 53 int number_count; 54 long long int count_of_result=0LL; 55 56 std::cin >> digit >> number; 57 58 if((digit <= 0) || ( digit >15)) 59 { 60 cout << "Illegal number" << endl; 61 return -1; 62 } 63// 重複確認 64 for(int i = 0;i<10;i++) 65 used_number[i]= 0; 66 for(int i = 0,number_count =0;i<strlen(number);i++) 67 { 68 if((number[i] >= '0')&&(number[i] <='9')) 69 { 70 if( used_number[(number[i]-'0')] == 0 ) 71 { 72 number_of_count++; 73 used_number[(number[i]-'0')]=1; 74 } 75 } 76 else { 77 cout << "Illegal number" << endl; 78 return -1; 79 } 80 } 81// 解の数を算出 82 work = check_number(ditit); 83// 84 if( work & 1 ) //解が奇数 85 { // 答えは2つ 86 cout << calc_number(work/2) << "," << calc_number((work/2)+1) << endl; 87 } else 88 { // 答えは1つ 89 cout << calc_number(work/2) << endl; 90 } 91}

机上なのでバグあるだろうなぁ。

投稿2016/11/19 08:14

編集2016/11/19 09:58
MasahikoHirata

総合スコア3747

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MasahikoHirata

2016/11/19 08:26

桁数が15桁までで、C++なので無理に文字列ではなくてlong long int型を使ってと考えてコーディング中。
guest

0

数字でなく文字列として処理するといいと思います。

1 該当する文字列の集合を作る。
2 ソートする。
3 中央のものを選ぶ。

まあ、集合を作らずに答えを導けるならば、なお良いですね。

投稿2016/11/19 08:05

HogeAnimalLover

総合スコア4830

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

s8079

2016/11/19 09:11

すでに数字でなく文字列として処理しています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問