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

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

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

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

Q&A

解決済

4回答

4417閲覧

「素数の足し算で」の解き方を教えてください

kuma_dansyaku

総合スコア18

C

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

0グッド

0クリップ

投稿2017/05/23 05:10

はじめに

いつもお世話になっております。
件名の解き方についてアドバイスを下さい。

これはCodeIQに投稿されていた問題だったのですが、どうしても解けませんでした。
結局解答のフィードバックなども無くどうやって解いたら良いのか困っています。

内容

今は閉じてしまって問題文すら読めなくなっていますがこんな感じの問題です。

素数の足し算で入力した値にいくつパターンがあるか出力してください(入力する素数の上限は100以下とする)

例は以下の通り

入力は以下のようにする

[最小値] [最大値] [和]

以下の場合だと2~19の間の素数の足し算で38になるパターンがいくつあるか出力する

2 19 38

19 + 17
3 + 5 + 11 + 19
2 + 3 + 5 + 11 + 17

なので答えは3

3

考え方

再帰を使うのだろうなと試してみましたがよく分からずに詰まっています。
因数分解ならなんとかなるんですがこれは難しすぎます。
考え方を教えてください。

C

1#if 0 2int getPrimeArray(int prime[]) { 3 int i, j; // counter 4 int num[25]; 5 6 // init 7 for (i = 0; i<ELMAX; num[i] = ++i); 8 9 // エラトステネスの篩(ふるい) 10 for (i = 0; i<ELMAX; i++) { 11 if (num[i] == 0) { 12 continue; 13 } 14 for (j = i + 1; j<ELMAX; j++) { 15 if (num[j] == 0) { 16 continue; 17 } 18 if (!(num[j] % num[i])) { 19 num[j] = 0; 20 } 21 } 22 } 23 24 // 配列に詰めていくよ 25 for (i = 1, j = 0; i<ELMAX; i++) { 26 if (num[i]) { 27 prime[j++] = num[i]; 28 } 29 } 30 return j; 31} 32#else 33 int prime[25] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 }; 34#endif 35 36 37 38 39int main() { 40 int imin, imax, ival; /* input */ 41 int tmp_val; /* temp */ 42 int min_el, max_el; 43 int i ; 44 45 // 標準出力からの入力は後回し 46 imin = 3; 47 imax = 19; 48 ival = 39; 49 50 // 51 tmp_val = (imax > ival)? ival : imax; 52 for (i = 0; _primenum[i] < tmp_val; i++); 53 max_el = i-1; 54 55 tmp_val = imin; 56 for (i = 24; tmp_val <= _primenum[i]; i--); 57 min_el = i; 58 59 printf("min[%d]=%d - max[%d]=%d\n", min_el, _primenum[min_el], max_el, _primenum[max_el]); 60 61 // 8(4+4) と 6(3+3) 以外の数は必ず解がある 62 63 return 0; 64}

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

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

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

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

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

A.Ichi

2017/05/24 01:07 編集

上記のケースの4つの素数組み合わせでは、他に2個有りますが、これらは対象ではないのでしょうか? 3 + 5 + 11 + 19 = 38、 3 + 7 + 11 + 17 = 38、 3 + 5 + 13 + 17 = 38 19 + 17 = 36 です。
guest

回答4

0

ヒントだけですが。

38 = 2 * 19がありませんね?
つまり、同じ素数は使わない、という条件がこの問題にはあります。

2を使わない、3を使わない、5を使わない、…
2を使う、3を使わない、5を使わない、…
2を使わない、3を使う、5を使わない、…

こんな感じで、全部の組合せを調べて条件を満たす組合せの数を数えれば答えられます。
計算量は高々2^25回のループです。

数がもっと大きいと、別のアルゴリズムが必要になります。(思いついたら追記します。)

思いついたので追記:38で例示します。

38-37 = 1
1は37未満の素数の組で書けない
38-31 = 7 (ok)
7-5 = 2
∴ 38-31-5 = 2 (ok)
2は5未満の素数の組で書けない。
7-3 = 4
4-2 = 2
2は2未満の素数の組で書けない

再帰を使えばこんな風に書けます。分解したい整数、分解に使える素数の最大値・最小値を関数に渡しましょう。

投稿2017/05/23 07:30

編集2017/05/23 13:17
majiponi

総合スコア1720

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

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

kuma_dansyaku

2017/05/25 11:25 編集

回答ありがとうございます。 参考になりました。
guest

0

ベストアンサー

アルゴリズムの問題は知らないと見当もつかない事がありますよね。
この内容でしたら、
1.初めから2~97までの素数列を用意する(配列など)

2-a.入力に応じて素数列を編集する(例えば、2から19までの素数列にする)
3-a.再帰関数を実行する

2-b.再帰関数の終了条件に、素数が入力で指定された範囲内に入っているかを判断する条件分岐を入れる(ソースでいえばp[i]<imin または p[i]>imax)

の2パターンが思いつきます。
Cのソースはパターンb, C++のソースはパターンaで作りました。
Cの再帰関数funcの動作については、ほとんどmajiponiさんの説明のとおりです。
"素数の和がsumになる組み合わせを探す"、という処理を"sumから素数を引いた結果が0になる組み合わせを探す"という処理に変えています。これは別にどちらでもいいと思います。
再帰関数を使うときは終了条件をどうするのか、求めたい値を外部変数に保存するのか、返り値にするのかを考えられると使えるようになるんじゃないかと思います。

最後に

入力が 2 19 38 の場合の答えは 6 になると思います。
再帰関数は深さ優先探索と幅優先探索の勉強をするといいと思いますよ。
頑張ってください!

以下、ソースコード

c

1#include "stdio.h" 2 3int p[25] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 }; 4int imin, imax, ival; /* input */ 5 6int func(int sum, int i){ 7 if(p[i]<imin) return func(sum,i+1); // 素数が最小値未満なのでインデックスをすすめる 8 if(sum==0) return 1; // 和が0なのでこの組み合わせは成功だった 9 if(sum<0) return 0; // 和が負になったのでこの組み合わせは失敗だった 10 if(p[i]>imax) return 0; // 素数が最大値より大きいので失敗。 11 return func(sum-p[i],i+1)+func(sum,i+1); // 現在のインデックスの素数p[i]をsumから引いた場合と引かなかった場合のそれぞれについて計算する。 12} 13 14int main() { 15 int i ; 16 17 // 標準出力からの入力は後回し 18 imin = 2; 19 imax = 19; 20 ival = 38; 21 // scanf("%d%d%d", &imin,&imax,&ival); 22 23 printf("%d",func(ival,0)); 24 25 return 0; 26}

c++

1#include <iostream> 2#include <cmath> 3#include <vector> 4 5using namespace std; 6 7int minn,maxn,sum; 8vector <int> p; 9 10// エラトステネスの篩 グローバル変数バージョン 11void prime(int n){ 12 vector <bool> arr(n); 13 fill(arr.begin(),arr.end(),true); 14 arr[0]=false; 15 arr[1]=false; 16 for(int i=2; i<sqrt(n); i++){ 17 if(arr[i]){ 18 for(int j=2; i*j<n; j++){ 19 arr[i*j]=false; 20 } 21 } 22 } 23 for(int i=2; i<n; i++){ 24 if(arr[i]){ 25 p.push_back(i); 26 } 27 } 28 if(arr[91])cout << "91" << endl; 29} 30// 2~97までの素数を必要な分だけにする 31void slice_prime(){ 32 vector <int> temp = p; 33 p.clear(); 34 for(int i=0; i<temp.size(); i++){ 35 if(temp[i]>=minn&&temp[i]<=maxn)p.push_back(temp[i]); 36 } 37} 38 39// 再帰関数。2から順番に素数をsumから引いた場合と引かなかった2つの場合について再起を行う。 40// 終了条件はsumが0(丁度和がsumになった場合)とsumが負(和がsumにならなかった場合)、そしてインデックスiがvector pのサイズを越えた(最大素数maxnのインデックスを越えた)とき。 41int func(int sum, int i){ 42 if(sum==0) return 1; 43 if(sum<0) return 0; 44 if(i==p.size()) return 0; 45 return func(sum-p[i],i+1)+func(sum,i+1); 46} 47 48 49int main() { 50 // 入力 51 cin >> minn >> maxn >> sum; cin.ignore(); 52 // 素数生成 53 prime(100); 54 // 素数をminn以上maxn以下のものだけにする 55 slice_prime(); 56 // 再帰関数を実行する 57 cout << func(sum,0) << endl; 58 return 0; 59}

投稿2017/05/23 16:30

chiwakii

総合スコア39

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

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

kuma_dansyaku

2017/05/25 11:28 編集

回答ありがとうございます。 頂いたソースを見てここまでシンプルになるんだ驚いています。 A.Ichiさんのソースと共に再帰を使ったアプローチはとても参考になりました。 教えて頂いた深さ優先探索と幅優先探索を検索して勉強いたします。
guest

0

順列組み合わせのプログラムのパターンを使って計算した結果で [和]と同じになるものを表示しています。

#include <stdio.h> #define MAX 100 #define N 16 int prms[48]={0}; int k=0; int buffer[N]; int prime_make(int prms[]) { int i,j,yaku; for(i=1;i<=MAX;i++){ yaku=0; for(j=1;j<=i;j++){ if(i%j==0) yaku++; } if(yaku==2) prms[k++]=i; } return k; } void print_combination(int n, int a) { int i,j; j=0; for (i = 0; i < n; i++){ j+=prms[buffer[i]-1]; } if (j==a){ for (i = n-1; i >= 0 ; i--) printf("[%d]",prms[buffer[i]-1]); printf("\n"); } } void comb_sub(int n, int r, int m, int a) { int i; if (r == 0) print_combination(m, a); else if (n == r) { for (i = r; i > 0; i--) buffer[m++] = i; print_combination(m, a); } else { comb_sub(n - 1, r, m, a); buffer[m] = n; comb_sub(n - 1, r - 1, m + 1, a); } } void combinations(int p, int q, int a, int r) { int n=q-p+1; if (r > 0 && r <= N) comb_sub(n, r, 0, a); } int main(void) { int i,j,k,m; int p,q; j=prime_make(prms); for (i=0;i<j;i++){ printf("%d ", prms[i]); } int st, ed, an; printf("\n"); while(1){ printf("input [MIN] [MAX] [TTL]\n"); scanf("%d%d%d", &st, &ed, &an ); printf("input no %d %d %d\n", st, ed, an); p=q=-1; for (i=0;i<j;i++){ if(prms[i]==st) p=i; if(prms[i]==ed) q=i; } if (p==-1 || q==-1) break; printf("select no %d %d %d\n", prms[p], prms[q], an); for (m=2;m<q-p;m++) combinations(p,q,an,m); } return 0; }

投稿2017/05/23 10:09

A.Ichi

総合スコア4070

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

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

kuma_dansyaku

2017/05/25 11:20 編集

回答ありがとうございます。 ソースコードまで添付して頂き本当にありがとうございます。 とても参考になりました。
guest

0

レスをくれた皆さん、丁寧に回答して頂き本当にありがとうございました。

頂いた回答の中にこの問題は幅優先探索 or 深さ優先探索を勉強すると良いと教えていただいたので調べてみました。
その上で解った事をここに書いてお礼に代えさせて頂きます。

幅優先探索 Wikipediaで見るとこんな感じ。
何故これで今回の問題が解決するのか解らない。

パターンを図に当てはめてみるとこんな感じかな。

![イメージ説明

取りあえず図解にすると何となく解る。
幅優先探索を探すとキューによる実装ばかりが見つかる。
深さ優先探索を探すとスタックを使った再帰ありと再帰なしの方法が見つかる。

この事から
幅優先探索⇒キューを使った手法
深さ優先探索⇒スタックを使った手法

深さ優先探索の計算量は幅優先探索より小さい( 最悪のケースでは同じ )

と言う認識を得た。

幅優先探索で実装してみると意外と簡単に出来た(ただキューのバッファサイズを検討する必要あり)

C

1#include <stdio.h> 2 3#define TRUE (1) /* 成功 */ 4#define FASLE (0) /* 失敗 */ 5 6#define QUEUE_SIZE (33554432) /* キューの最大数(2^25) *//* 最悪のケースのみ */ 7 8int _queue_data[QUEUE_SIZE]; /* キュー格納バッファ */ 9int _queue_head = 0; /* キューの先頭位置 */ 10int _queue_num = 0; /* キューの使用数 */ 11 12int enqueue(int enq_data) { 13 if ((_queue_head + _queue_num) < QUEUE_SIZE) { 14 _queue_data[(_queue_head + _queue_num) % QUEUE_SIZE] = enq_data; 15 _queue_num++; 16 return TRUE; 17 } 18 else { 19 return FASLE; 20 } 21} 22 23int dequeue(int *deq_data) { 24 if (_queue_num) { 25 *deq_data = _queue_data[_queue_head]; 26 _queue_num--; 27 _queue_head = (_queue_head + 1) % QUEUE_SIZE; 28 return TRUE; 29 } 30 else { 31 return FASLE; 32 } 33} 34 35 36#define N (25) 37int prime[N] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 }; /* 1-100 までの素数たち */ 38 39int main() { 40 int i, n, match_cnt; 41 int imin, imax, ival, sum; 42 43 printf("input [MIN] [MAX] [TTL]\n"); 44 scanf_s("%d%d%d", &imin, &imax, &ival); 45 printf("input no %d %d %d\n", imin, imax, ival); 46 47 for (i = 0; imin > prime[i]; i++); 48 enqueue(0); // 加算しない値 49 enqueue(prime[i++]); // 加算した値 50 n = _queue_num; 51 match_cnt = 0; 52 53 while (_queue_num && prime[i] <= imax && i<N) { // キューが空っぽになるか参照素数がimaxを越えるか素数要素を越えるまで繰り返す 54 dequeue(&sum); 55 n--; 56 57 if (sum == ival || (sum + prime[i]) == ival) { 58 match_cnt++; // 一致した値 59 continue; 60 } 61 if (sum < ival) enqueue(sum); // 素数を加算しない値 62 if ((sum + prime[i]) < ival) enqueue(sum + prime[i]); // 素数を加算した値 63 if (n == 0) { 64 i++; 65 n = _queue_num; 66 } 67 } 68 printf("match=%d\n", match_cnt); 69 70 return 0; 71}

深さ優先探索は以下の通り(まずは再帰を使わない方法で基礎を理解しようと思います)

C

1#include <stdio.h> 2 3#define TRUE (1) /* 成功 */ 4#define FASLE (0) /* 失敗 */ 5 6#define STACK_SIZE (1000) /* スタックの最大数 *//* 最悪のケースっていくつなんだろう? */ 7 8int _stack_data[STACK_SIZE]; /* スタック格納バッファ */ 9int _stack_num = 0; /* スタックの使用数 */ 10 11int push(int stack_data) { 12 /* TBD */ 13 return TRUE; 14} 15 16int pop(int *stack_data) { 17 /* TBD */ 18 return TRUE; 19} 20 21 22#define N (25) 23int prime[N] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 }; /* 1-100 までの素数たち */ 24 25int main() { 26 /* TBD */ 27 return 1; 28}

投稿2017/05/25 11:41

kuma_dansyaku

総合スコア18

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問