問題
正整数 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ページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答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) で求められます。指数法則をうまく使って答えを導き出してください。
追記
多倍長演算の乗算は必要ないかもしれません。3 は 2 進数で 11 なので、何かに 3 をかけるということは、その数を左に 1 ビットシフトしたものとその数自身を足したものに等しくなります。ビットシフトと加算を 165 回繰り返すだけなので、十分高速に結果が求まると思います。また、3 にビットシフトを 164 回繰り返すと 166 ビットなので、そこに加算した繰上りを入れても 167 ビットつまり 21 バイトに収まります。バッファは計算しながら動的に確保しなくても、21 バイト固定で間に合うはずです。後の問題はそれを 10 進数に直す際の除算ですね。しかしここまで十分高速に計算できているので、間に合いそうな気がします。
投稿2020/05/23 11:02
編集2020/05/23 15:41総合スコア28669
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
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/23 07:06
退会済みユーザー
2020/05/23 10:41
2020/05/24 01:29
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
総合スコア416
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/24 01:10
2020/05/24 01:30
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
総合スコア591
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/23 08:06
2020/05/25 23:03
0
学校の課題か何かだと思うので、敢えて捻くれた回答をします。
この課題文中では、アウトプットフォーマットについて指定がありません。(出力例はあくまでも例です。)また、「n桁」という指定はありますが、これは10進数における桁数とは書かれておりません。これを利用し、「アウトプットフォーマットとして3進数を採用する」という旨を断り書きすれば、簡単に回答できます。
3のベキ乗数は、3進数では100000・・・というキリ番となります。
したがって、3のベキ乗数は桁数毎に一つずつしか存在しません。しかも、それぞれ0の数が違うだけです。
例
入力:5
出力:10000(3進数として表示)
入力:10
出力:1000000000(3進数として表示)
入力:20
出力:10000000000000000000(3進数として表示)
カンマ区切りにすると読みやすいかもしれません。
投稿2020/05/23 14:56
総合スコア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
総合スコア21739
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総合スコア5406
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/23 06:03
2020/05/24 03:59
2020/05/25 11:04
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2020/05/23 14:16
2020/05/23 14:43
2020/05/24 01:14