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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

7回答

1247閲覧

最大79桁の数字の扱い方

grape_ll

総合スコア83

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2020/05/23 01:33

編集2020/05/23 06:56

問題

正整数 n ( 0 < n < 80 ) に対して, n桁以内の最大の3のベキ乗数を左詰めで書き出すプログラムを作れ。

【入力】
正整数 n ( 0 < n < 80 ) 1つ。

【出力】
1行として書き出す(末尾に改行を書き出すこと)。

【例】
入力1
1
出力1
9

入力2
6
出力2
531441

質問内容

下記のように計算したら実行時間がオーバーしてしまいました。

コード

C

1#include<stdio.h> 2int main(void){ 3 int n; 4 scanf("%d",&n); 5 int order[80]={9}; 6 int flag[80]={0}; 7 int i; 8 while(order[n]==0&&order[n+1]==0){ 9 for(i=0;i<n;i++){ 10 order[i]*=3; 11 order[i]+=flag[i]; 12 flag[i]=0; 13 if(order[i]>9){ 14 flag[i+1]+=order[i]/10; 15 order[i]%10; 16 } 17 } 18 i++; 19 } 20 21 for(i=n-1;i>=0;i--){ 22 printf("%d",order[i]); 23 } 24 printf("\n"); 25 return 0; 26 27}

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

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

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

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

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

guest

回答7

0

定義

正の実数 a, b, c において、a の b 乗が c である時、a を底、b を指数、c を冪と呼ぶことにします。この時、架空の関数 p は p(a, b) において c を返すものとします。さらに架空の関数 l は、l(a, c) において b を返します。この時、a を底、c を真数、b を対数と呼びます。

3 の累乗である n 桁の最大値を求める

3 の累乗である n 桁の最大値 x は 3 を底とする冪なので、p(3, t) で表すことができます。また、これを 10 を底とする冪と考えると、p(10, d) で表すこともできます。x = p(3, t) = p(10, d) です。

なぜ 10 を底とする冪を考えるかというと、これで 10 進数における桁数が求められるからです。例えば 1 は p(10, 0) であり、10 は p(10, 1) であり、100 は p(10, 2) です。よって、冪が一桁である時、指数は 0 以上 1 未満となり、二桁である時、1 以上 2 未満、三桁である時、2 以上 3 未満となります。たとえば二桁の数 50 が冪の時、指数は約 1.69897000433602 となり、p(10, 1.69897000433602) ≒ 50, l(10, 50) ≒ 1.69897000433602 となります。

話を戻して x = p(3, t) = p(10, d) ですが、ここで 10 = p(3, l(3, 10)) なので、これを代入すると x = p(3, t) = p(p(3, l(3, 10)), d) となります。

冪乗の指数法則より、p(3, t) = p(3, l(3, 10) * d) が成り立つため、t = l(3, 10) * d となります。ここで l(3, 10) は定数約 2.09590327428938 であり、d = t / l(3, 10) が成り立ちます。ここで d に n を代入することにより、t = n * 2.09590327428938 で求めることができます。

結論として、実数 a の小数点以下を切り捨てる架空の関数 int を考えた時、求める数は p(3, int(n * 2.09590327428938)) です。

n = 79 の時の 3 の累乗の最大値

p(3, int(79 × 2.09590327428938)) = p(3, 165) = 5308930362839928667688049530226627139643793175493798707037516219525092562313843

が答えになります。

時間短縮

多倍長演算を用いて 3 を 165 回乗算すれば解は求まるわけですが、これを更に短縮しましょう。例えば p(3, 4) は真正直に 3 を 4 回乗算しなくても、p(p(3, 2), 2) で求められます。指数法則をうまく使って答えを導き出してください。

指数法則 - Wikipedia

追記

多倍長演算の乗算は必要ないかもしれません。3 は 2 進数で 11 なので、何かに 3 をかけるということは、その数を左に 1 ビットシフトしたものとその数自身を足したものに等しくなります。ビットシフトと加算を 165 回繰り返すだけなので、十分高速に結果が求まると思います。また、3 にビットシフトを 164 回繰り返すと 166 ビットなので、そこに加算した繰上りを入れても 167 ビットつまり 21 バイトに収まります。バッファは計算しながら動的に確保しなくても、21 バイト固定で間に合うはずです。後の問題はそれを 10 進数に直す際の除算ですね。しかしここまで十分高速に計算できているので、間に合いそうな気がします。

投稿2020/05/23 11:02

編集2020/05/23 15:41
Zuishin

総合スコア28660

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

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

退会済みユーザー

退会済みユーザー

2020/05/23 14:16

どうも数学やアルゴリズム系弱いので、こういうの頭の中でパパっと出てこないですね…興味深いです
Zuishin

2020/05/23 14:43

途中自分で作った関数の引数の順番がわからなくなって間違えていました。 5308930362839928667688049530226627139643793175493798707037516219525092562313843 は 3 の冪乗数だしこれに 3 をかけると 80 桁になるので合っているんじゃないかなとは思いますが。n = 1, 2, 3, 4, 5 の時も合っているようです。後は確かめていません。
grape_ll

2020/05/24 01:14

数学的にみればこのようなアルゴリズムで解くことが出来るのですね。 参考になります。
guest

0

ベストアンサー

正解を書こうと思えば書けますが、なんか学校の課題っぽいので、その実現方法を考えるのがテーマだと思いますよ。
(一般的には、大きい数を扱える多倍長演算ライブラリ等を使いますが)

考え方のヒントとしては、例えば一発で79桁を計算するのは無理でも、
・計算可能な桁数(例えば5桁づつ)ごと配列に分けて計算する
・桁上がりを自力で処理する
・べき乗は単純な掛け算の繰り返しである(ループで処理できる)
というように、細かい要素に分けてやれば可能ですよね。


参考例として。
ちなみに、C#で適当に書くとこんなんなります。(速度とか効率はとりあえず置いときます)

C#

1int n = 79; //とりあえずnの最大を与える 2int powX = 3; 3int powY = 1; 4BigInteger val = powX; //出力する数を格納する変数 5 6while (true) 7{ 8 //次のBigIntegerの桁数を計算する 9 //Log10(valNext)+1=80(桁数80)に達した時点で、valに79桁の最大の数が格納されている 10 var valNext = BigInteger.Pow(powX, powY); 11 if (((int)BigInteger.Log10(valNext) + 1) > n) 12 { 13 break; 14 } 15 val = valNext; 16 powY++; //べき乗する値を加算 17} 18 19//コンソールに文字列を出力 20Console.WriteLine(val.ToString());

C#なんて判らん!となるでしょうが、これをC言語の機能に置き換えると、
0. 大きい数を扱う構造体(BigIntegerを置き換える)
0. 上記構造体をべき乗(乗算)する関数(BigInteger.Powを置き換える)
0. 上記構造体の桁数を計算する関数(BigInteger.Log10+1を置き換える)
0. 上記構造体を文字列に変換する関数(val.ToString()を置き換える)

を用意すると、同等の機能を実現できます。

C

1typedef struct BigInt 2{ 3 int Values[20]; 4} BigInt; 5 6int main() 7{ 8 char buf[100]; 9 int n = 79; 10 int powX = 3; 11 int powY = 1; 12 BigInt val = { powX }; 13 14 while (true) 15 { 16 BigInt valNext = BigPow(powX, powY); 17 if (BigIntLength(valNext) > n) 18 { 19 break; 20 } 21 val = valNext; 22 powY++; 23 } 24 25 BigIntToString(buf, val); 26 printf("%s\r\n", buf); 27 return 0; 28}

投稿2020/05/23 03:26

編集2020/05/23 14:06
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

grape_ll

2020/05/23 06:02

一つの変数に入らない時点で思考が止まってしまっていたので,考え方のヒントをくださるだけでとても助かります。これらのヒントをもとに考えてみます。
grape_ll

2020/05/23 07:06

5けたずつだと桁数の判定が難しいかと考え,1桁ずつでやってみたのですが実行時間がオーバーしてしまいました。このことを考えるとまとめる桁数を上げた方がいいのだろうという結論に至るのですが、まとめた場合にどう止めればいいのかを教えていただけると助かります。 #include<stdio.h> int main(void){ int n; scanf("%d",&n); int order[80]={9}; int flag[80]={0}; int i; while(order[n]==0&&order[n+1]==0){ for(i=0;i<n;i++){ order[i]*=3; order[i]+=flag[i]; flag[i]=0; if(order[i]>9){ flag[i+1]+=order[i]/10; order[i]%10; } } i++; } for(i=n-1;i>=0;i--){ printf("%d",order[i]); } printf("\n"); return 0; }
退会済みユーザー

退会済みユーザー

2020/05/23 10:41

条件にうまく掛からずに、どこかで無限ループしてるのではないですか。 変数の値が期待通りに変化しているか、デバッガで止めて変数の値を見たり、printf等で変数の値を出力したりして確認しましょう。
grape_ll

2020/05/24 01:29

問題が分かりました。 for文でnを含んでいなかったので無限ループしていたみたいです。 下記のコードで大丈夫でした。 ありがとうございました。 #include<stdio.h> int main(void){ int n; scanf("%d",&n); int order[80]={9}; int flag[80]={0}; int ans[80]; int i,j; while(order[n]==0&&order[n+1]==0){ for(i=0;i<=n;i++){ ans[i]=order[i]; order[i]*=3; order[i]+=flag[i]; flag[i]=0; printf("(%d,%d)\n",order[i],i); if(order[i]>9){ flag[i+1]+=order[i]/10; order[i]%=10; } } } for(i=n-1;i>=0;i--){ printf("%d",ans[i]); } printf("\n"); return 0; }
guest

0

79桁なので1桁ずつの計算でよいと思います。
(ぱっと見、order[i]%10;の行がおかしいです。)

参考
int型での計算をもとに、多倍長整数(8桁ごと)での計算に書き換えたコードです。
多倍長整数の計算部分を関数化すると、mainはint型での計算と同じように書けます。

C

1#include <stdio.h> 2#include <string.h> 3#include <math.h> 4 5#define SIZE 10 6#define DIG 8 7#define NUM 100000000 /* 10^8 */ 8 9int my_log10(int* val); 10void my_mul(int* val, int n); 11void my_print(int* val); 12 13int main(void) { 14 int n; 15 scanf("%d", &n); 16 // 参考:int型での計算 17 if (n <= DIG) { 18 int result = 1; // 最初の値 19 int next = 3; // 次の値 20 while ((int)log10(next) < n) { // 次の値がn桁を超えるまで 21 result = next; // 次の値を結果に設定 22 next *= 3; // 次の値に3をかける 23 } 24 printf("%d\n", result); // 結果を出力 25 } 26 // 多倍長整数での計算 27 else { 28 int result[SIZE] = { 1 }; // int result = 1; 29 int next[SIZE] = { 3 }; // int next = 3; 30 while (my_log10(next) < n) { // while ((int)log10(next) < n) 31 memcpy(result, next, sizeof(result)); // result = next; 32 my_mul(next, 3); // next *= 3; 33 } 34 my_print(result); // printf("%d\n", result); 35 } 36 return 0; 37} 38 39//桁数 40int my_log10(int* val) { 41 int i; 42 for (i = SIZE - 1; val[i] == 0 && i > 0; i--); 43 return (int)log10(val[i]) + DIG * i; 44} 45 46//かけ算 47void my_mul(int* val, int n) { 48 int i; 49 int tmp = 0; 50 for (i = 0; i < SIZE; i++) { 51 tmp = val[i] * n + tmp; 52 val[i] = tmp % NUM; 53 tmp /= NUM; 54 } 55 return; 56} 57 58//出力 59void my_print(int* val) { 60 int i; 61 for (i = SIZE - 1; val[i] == 0 && i > 0; i--); 62 printf("%d", val[i]); 63 for (i--; i >= 0; i--) { 64 printf("%0.*d", DIG, val[i]); 65 } 66 printf("\n"); 67 return; 68}

投稿2020/05/23 12:16

etsuhisa

総合スコア416

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

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

grape_ll

2020/05/24 01:10

コードのご指摘ありがとうございます。%=に直してみました。しかしこれだけではまだループが終わっていないようだったので、他の原因も探してみようと思います。 また,参考のコードもありがとうございます。 ですが、ひとまずは自分のコードを仕上げてみようと思います。 まだほかにも指摘等がありましたらぜひ教えてください。よろしくお願いいたします。
grape_ll

2020/05/24 01:30

for文の条件がだめな原因だったみたいです。 無事解決しました。ありがとうございました。 #include<stdio.h> int main(void){ int n; scanf("%d",&n); int order[80]={9}; int flag[80]={0}; int ans[80]; int i,j; while(order[n]==0&&order[n+1]==0){ for(i=0;i<=n;i++){ ans[i]=order[i]; order[i]*=3; order[i]+=flag[i]; flag[i]=0; printf("(%d,%d)\n",order[i],i); if(order[i]>9){ flag[i+1]+=order[i]/10; order[i]%=10; } } } for(i=n-1;i>=0;i--){ printf("%d",ans[i]); } printf("\n"); return 0; }
guest

0

たかだか 79 パターンの入力しかない上に、連続した自然数ということは、事前に全てのnに対応する
答えを配列に突っ込んでおけばO(1)で答えを書き出せますね。

・・・これだけだと怒られそうなので、下記の手順を追加提示しておきます。

A.元の数字の文字列sourceの桁数nを数える/例:source="386" → n=3
B.要素数n+1の配列tempを作る/例:temp[4]。temp={0,0,0,0}
C.tempの各桁にsourceの各桁の3倍の数字を入れてまわる/例:temp={0,9(33),24(83),18(6*3)}
D.tempに対して繰り上がり処理を行う
/例:temp={0,9,25(24+1),8} → temp={0,11(9+2),5,8} → temp={1,1,5,8}
E.配列tempから文字列answerに変換/例:answer="1158"(※最上位桁が0なら詰める)

投稿2020/05/23 06:15

himazin.blm

総合スコア581

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

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

grape_ll

2020/05/23 08:06

ここでいう最初のもとの数字とはどのことを示していますでしょうか。 そこから先の進行はなんとなくわかるのですが、そこが分からないので最後のanswerが何を表しているのかが予想できていません。4桁ならば1158*3だと思うのですが。 よろしくお願いいたします。
himazin.blm

2020/05/25 23:03

「最初のもとの数字」は「3倍したい数字」です。上の例だと386のことですね。この手順で3の乗数を80桁になるまで全部計算しておいて、答えの配列に突っ込む数をそこから拾えば良い、という話です。
guest

0

学校の課題か何かだと思うので、敢えて捻くれた回答をします。

この課題文中では、アウトプットフォーマットについて指定がありません。(出力例はあくまでも例です。)また、「n桁」という指定はありますが、これは10進数における桁数とは書かれておりません。これを利用し、「アウトプットフォーマットとして3進数を採用する」という旨を断り書きすれば、簡単に回答できます。

3のベキ乗数は、3進数では100000・・・というキリ番となります。

したがって、3のベキ乗数は桁数毎に一つずつしか存在しません。しかも、それぞれ0の数が違うだけです。

入力:5 
出力:10000(3進数として表示)

入力:10
出力:1000000000(3進数として表示)

入力:20
出力:10000000000000000000(3進数として表示)

カンマ区切りにすると読みやすいかもしれません。

投稿2020/05/23 14:56

HogeAnimalLover

総合スコア4830

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

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

0

himazin.blm さんの目から鱗な発想

たかだか 79 パターンの入力しかない上に、連続した自然数ということは、事前に全てのnに対応する

答えを配列に突っ込んでおけばO(1)で答えを書き出せますね。

を参考に、Cのプログラムを作るRubyのプログラムを作ってみました。

Ruby

1n = 1 2list = 80.times.map do |i| 3 n *= 3 while n < 10**i 4 n / 3 5end 6 7puts '#include <stdio.h>' 8print 'static char *list[] = {' 9print list.map{|i| "\"#{i}\""}.join(",") 10puts '};int main(void){int n;if(scanf("%d",&n)!=1||n<=0||n>=80)return 1;printf("%s\n",list[n]);return 0;}'

計算量O(1)とは素晴らしいです。

投稿2020/05/23 07:53

raccy

総合スコア21735

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

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

grape_ll

2020/05/23 08:00

申し訳ございません。私の知識不足でRubyの書き方はまったくわかりません。 しかし、この質問を見た他の人にはわかるかもしれません。 ご回答ありがとうございました。
guest

0

小学生の算数の計算のように、一桁ずつchar型配列に入れて計算してみてはいかがですか?
理屈が分かれば、long型配列にして効率的に計算することもできます。

  456 *   3 ーーーーー +  18 + 15 +12 ーーーーー =1368

radianさんのコメントに書かれたコードを改良してみました。

c

1#include <stdio.h> 2 3int main(void) { 4 int n; 5 scanf("%d", &n); 6 int value[80] = {9}; 7 int answer[80]; 8 int carry = 0; 9 while (carry == 0) { 10 for (int i = 0; i < n; i++) { 11 answer[i] = value[i]; // copy last value to answer 12 carry += value[i] * 3; 13 value[i] = carry % 10; 14 carry /= 10; 15 } 16 } 17 for (int i = n - 1; i >= 0; i--) { 18 printf("%d", answer[i]); 19 } 20 printf("\n"); 21 return 0; 22}

投稿2020/05/23 01:57

編集2020/05/23 10:34
shiracamus

総合スコア5406

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

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

grape_ll

2020/05/23 06:03

数を分離するやり方で考えてみようと思います。 ご助言ありがとうございます。
shiracamus

2020/05/24 03:59

改良コードを追記しました。
grape_ll

2020/05/25 11:04

ご丁寧にどうもありがとうございます。 参考にさせていたきますね。 今後とも機会があればぜひよろしくお願いいたします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問