• ベストアンサー

組合せ

2個の整数m,n(m≧n)をキーボートから受け取って、m個の相異なる物の中からn個取り出す組合せの数を計算するプログラムを作っているんですが、下のプログラムだと、13の階乗でオーバーフローしてしまいました。combination関数を使わずに、13の階乗を計算したいのですが…。 13!/(k!(13-k)!)で、13!でオーバーフローなので、13!/k!=(k+1)×…×13を計算すればいいのは分かるのですが、どういう関数を定義すればいいのかわかりません。 ヒントやアドバイス頂けると、助かります。 よろしくお願いします。 #include <stdio.h> void main(void) {long m,n; long fact(long); while(scanf("%ld,%ld",&m,&n)!=EOF) printf("comb(%ld,%ld)=%ld\n",m,n, fact(m)/(fact(n)*fact(m-n))); } long fact(long k) {long i,f; f=1; for (i=1;i<=k;i=i+1) f=f*i; return(f); }

質問者が選んだベストアンサー

  • ベストアンサー
  • php504
  • ベストアンサー率42% (926/2160)
回答No.3

combination関数を使わずといのは再帰関数を使わないでということでしょうか n!か(m - n)!の大きい方で約分する関数を作りました /* mCn を計算する関数 */ int combi(int m, int n) { int i, numerator, denominator; if (m < n || n <= 0 || m <= 0) { return 0; } numerator = 1; denominator = 1; if (m > n / 2) { // 分子 n!部分は計算しない for (i = m; i >= (n + 1); i--) { numerator *= i; } // 分母 (m - n)!のみ計算 for (i = m - n; i > 0; i--) { denominator *= i; } } else { // 分子 (m - n)!部分は計算しない for (i = m; i >= (m - n + 1); i--) { numerator *= i; } // 分母 n!のみ計算 for (i = n; i > 0; i--) { denominator *= i; } } return (numerator / denominator); }

juck0808
質問者

お礼

回答ありがとうございます。 丁寧なアドバイス、ありがとうございます。 参考にさせて頂きます!

その他の回答 (6)

  • yama5140
  • ベストアンサー率54% (136/250)
回答No.7

(暇な年寄りが我流で、半日かけて・・) >13の階乗でオーバーフローしてしまいました。  そうなんですよね。windows 電卓だって、29! までできるのに・・。  そこで、1つの変数に入れるのではなく、    ★10進数4桁ごとに配列に入れる方法で、階乗を《表示するだけの》プログラムを投稿します(↓パクリ)。  http://bal4u.dip.jp/algo/factorial.txt    ★また、nCr で、分母と分子それぞれ、階乗する数を(掛け合わせる前に)素因数分解して、   分母と分子の各素因数の使用共通数を「掛けない」という「手」で nCr を求めるプログラムを作ってみました。      99C88 ( r < n < 100 )まで求まり?ますが、いかんせん検算できません(残念)。      http://www.ccad.sist.chukyo-u.ac.jp/~mito/syllabi/proba/enum/index.htm      ↑ここで、順列・組み合わせをお復習いしつつ、いくつかは検証したんですが・・。      実行例 >hoge 20 13 ---------------------------------------- #include <stdio.h> #include <stdlib.h> #define KAI 0 // 階乗 #define JUN 1 // 順列 #define BUNSI 0 // 分子 #define BUMBO 1 // 分母 int igSoinSu[100], igSoinsuCnt[2][100] = { { 0 } }; int SoinsuBunkai( int k, int iSw, int iMax ) {  int i;  for( i = 2; i <= iMax; i++ ){   if( igSoinSu[i] ){ // 素因数で    if( k == i ){ // 素因数そのもの     igSoinsuCnt[iSw][i]++; // 使用数増加     return( 1 );    }    if( ! ( k % i ) ){ // 素因数で約せるか     igSoinsuCnt[iSw][i]++; // 使用数増加     k /= i;     SoinsuBunkai( k, iSw, iMax ); // 再帰     return( k );    }   }  }  return( 0 ); } int Get4Keta( int *iKai4, int iNNN, int iRRR, int iSw ) {  int k, j, iEnd = 0, iUp, iLoopEnd = 2;  int *ipLeft, *ipMigi;  long lWork;  if( JUN == iSw ) iLoopEnd = iNNN - iRRR + 1;  *iKai4 = 1;  for( k = iNNN; k >= iLoopEnd; k-- ){   iUp = 0; // 桁上がり用   ipLeft = iKai4;   for( j = 0; j <= iEnd; j++ ){    lWork = *ipLeft;    lWork = lWork * k + iUp;    *ipLeft++ = lWork % 10000;    iUp = lWork / 10000;   }   if( iUp ){ // 桁上がり    *ipLeft = iUp;    iEnd++; // 後方?へ   }  }  for( ipLeft = iKai4, ipMigi = iKai4 + iEnd; ipLeft < ipMigi; ipLeft++, ipMigi-- ){ // 尾頭入れ換え   iUp = *ipLeft;   *ipLeft = *ipMigi;   *ipMigi = iUp;  }  return( 1 + iEnd ); } long Get_nCr( int iNNN, int iRRR ) {  int k, i, iSa;  long lWork_B = 1, lWork_S = 1;  for( k = iRRR; k >= 2; k-- ){ // 分母の一部 r!   SoinsuBunkai( k, BUMBO, iRRR );  }  for( k = ( iNNN - iRRR ); k >= 2; k-- ){ // 分母の一部 ( n - r )!   SoinsuBunkai( k, BUMBO, ( iNNN - iRRR ) );  }  for( k = iNNN; k >= 2; k-- ){ // 分子 n!   SoinsuBunkai( k, BUNSI, iNNN );  }  for( k = 2; k <= iNNN; k++ ){ // 分母と分子の使用数の違いのみ掛け合わせ   if( igSoinsuCnt[BUMBO][k] == igSoinsuCnt[BUNSI][k] ) continue;   iSa = igSoinsuCnt[BUMBO][k] - igSoinsuCnt[BUNSI][k];   if( iSa > 0 ) for( i = 0; i < iSa; i++ ) lWork_B *= (long)k; // 不要?   if( iSa < 0 ) for( i = 0; i > iSa; i-- ) lWork_S *= (long)k;  }  if( lWork_S < 0 ){   printf( "r を n に近づけて下さい( r < n < 100 )\n" );   exit( 0 );  }  return( lWork_S / lWork_B ); } void SoinsuSet( void ) {  int i, j;  igSoinSu[0] = 0;  igSoinSu[1] = 0;  for( i = 2; i < 100; i++ ){   igSoinSu[i] = 1; // 素因数   for( j = 2; j <= ( i / 2 ); j++ ){    if( ! ( i % j ) ){ // でない     igSoinSu[i] = 0;     break;    }   }  } } void main( int argc, char *argv[] ) {  int n, iEnd_K, iEnd_J, iNNN, iRRR;  int iJun4[1000] = { 0 }; // 10進4桁格納  int iKai4[1000] = { 0 };  long lKekka;  SoinsuSet();  if( 3 != argc ) exit( 0 );  iNNN = atoi( argv[1] ); // nCr の n  iRRR = atoi( argv[2] ); // nCr の r  iEnd_K = Get4Keta( iKai4, iRRR, 0, KAI ); // r!  iEnd_J = Get4Keta( iJun4, iNNN, iRRR, JUN ); // nPr  printf( "%d! = %d", iRRR, iKai4[0] );  for( n = 1; n < iEnd_K; n++ ){   printf( "%04d", iKai4[n] );  }  printf( "\n" );  printf( "%dP%d = %d", iNNN, iRRR, iJun4[0] );  for( n = 1; n < iEnd_J; n++ ){   printf( "%04d", iJun4[n] );  }  printf( "\n" );  lKekka = Get_nCr( iNNN, iRRR ); // nCr  printf( "%dC%d = %ld\n", iNNN, iRRR, lKekka ); } 注:インデントに全角空白を用いています。タブに一括変換して下さい。

juck0808
質問者

お礼

回答、ありがとうございます。 参考にさせて頂きます!

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.6

#2 についてもうちょっというと, comb(n, k) = n! / (k! (n-k)!) をそのまま計算すると, 分子の n! でオーバーフローすることが必ず問題になります. で, 少し考えると「n! / k! = n (n-1) ... (k+1) を計算して (n-k)! で割る」というところにたどり着きますが, これでもやっぱり n! / k! で破綻する (最終結果はオーバーフローしないにもかかわらず計算途中でオーバーフローする) ことがあります. これを避ける単純な方法が「comb(n, 0) = 1 から comb(n, 1), comb(n, 2), ... と計算していく」というもので, そのためには comb(n, k) から comb(n, k+1) を計算できる必要があります. この線でがんばれば「結果がオーバーフローしない限り正しく計算できる」ようにもできるはず.

juck0808
質問者

お礼

アドバイスありがとうございます。 頑張ってみます! 参考にさせて頂きます!

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.5

えっと, if (n > m-n) n = m-n; とさいしょに一発入れておくだけでいいんじゃないでしょうか>#3.

  • php504
  • ベストアンサー率42% (926/2160)
回答No.4

訂正 if (m > n / 2) { ↓ if (n > m / 2) {

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

comb(n, k) と comb(n, k+1) の関係を考える.

回答No.1

分母と分子から同じ数を取り除いて約分します。

juck0808
質問者

お礼

回答ありがとうございます。 参考にさせて頂きます。

関連するQ&A

  • 組み合わせ プログラミング

    c言語についてです os linux コンパイラはgccです long fact2(int n,int m)を作成してfact2(n,m)を使って組み合わせを計算するプログラムを作れという問題で下記のように作りましたが コンパイルできません. エラーメッセージは 2-1.c:14: error: 関数 `fact2' への引数が少なすぎます 2-1.c:14: error: 関数 `fact2' への引数が少なすぎます 2-1.c:14: error: 関数 `fact2' への引数が少なすぎます です 関数のとこが違うと思うですが どうしたらいいのかわかりません それともなにか他のとこが違うのでしょうか? #include <stdio.h> long fact2(int,int); main() { int n, m; long c; printf(" nCm (n>m) \n"); printf("input n m ="); scanf("%d %d",&n, &m); c =fact2(n) / (fact2(m) * fact2(n-m)); printf("%dC%d = %ld\n",n,m,c); } long fact2(int n,int m) { int i; long c=1; for(i=1; i<=n; i++) c*=i; return(c); }

  • 困ってます…nCrを求めるC言語プログラミング

    nCr、つまりn個のうちr個を取り出すときの場合の数を求めるプログラミングを作りたいのですが、どうもうまくいきません。 関数combinationを作って求めるという指定もあり、自分で出来るとこまで作ってみたのですが訳がわからなくなってしまい、かなり困っています…; コンパイルは出来るのですが実行してもセグメントエラーが出るばかりで… すみませんがご指摘していただけないでしょうか…? #include<stdio.h> //階乗を計算する関数 int fact(int num){ int i; if(num < 0){ return -1; } else if(num == 0){ return 1; } else if(num == 1){ return 1; } else { i = num * fact(num - 1); return i; } } //コンビネーションを計算 int combination(int n, int r) { int fact(int num); int i; i=fact(n)/fact(r)/fact(n-r); return combination(n-1, r-1)-combination(n,r-1); } int main(void) { int n, r; while ( printf("n r を入力して下さい。"), scanf("%d%d", &n, &r) == 2 ) { printf("nCr(%d,%d)=%d\n", n, r, combination(n, r)); } return 0; }

  • 桁落ちのプログラムで真の値と計算結果

    #include <stdio.h> const double PI=3.141592653589793; double sum(long m) { double n,term,sum; n=1; sum=0; term= 1.0/ (n*n);/*初項*/ while( n<=m ){ sum+=term; n++; term= 1.0/ (n*n);/*次項の計算*/ } return sum; } /*この計算の答えはπ*/ int main(void) { double s; long m; int i; m=1; for(i=0;i<9;i++){ s=sum(m); printf("%2d m=%10ld sum= %22.16e err= %22.16e \n",i,m,s,s-PI*PI/6); m*=10;/*次は10倍にする*/ } return 0; で真の値と計算結果を調べるにはどうしたらいいのでしょうか?

  • 桁落ちの誤差について

    #include <stdio.h> const double PI=3.141592653589793; double sum(long m) { double n,term,sum; n=1; sum=0; term= 1.0/ (n*n);/*初項*/ while( n<=m ){ sum+=term; n++; term= 1.0/ (n*n);/*次項の計算*/ } return sum; } /*この計算の答えはπ*/ int main(void) { double s; long m; int i; m=1; for(i=0;i<9;i++){ s=sum(m); printf("%2d m=%10ld sum= %22.16e err= %22.16e \n",i,m,s,s-PI*PI/6); m*=10;/*次は10倍にする*/ } return 0; のプログラムで誤差が生じる理由ってなんなんでしょうか?

  • 組み合わせ

    n個の集合からp個を取る組み合わせの総数を出力するプログラムなんですが nCp=n!/p!(n-p)!という式を使い #include<stdio.h> int kaijo(int m); int comb(int n,int p); int main(void) { int i,j; printf("n="); scanf("%d",&i); printf("p="); scanf("%d",&j); printf("comb(%d,%d)=%d\n",i,j,comb(i,j)); } int kaijo(int m) { if(m>0) return(m*kaijo(m-1)); else return 1; } int comb(int n,int p) { if(n>0) return((n*kaijo(n-1))/(p*kaijo(p-1)*(n-p)*kaijo(n-p-1))); else return 1; } と書いてみたのですがこれではnが大きいとC言語のint型で扱える最大値を超えてしまい正しい結果が出力されません。  そこでint型を使ったままでnやpが大きい場合でもある程度出力できるようにしたいのですがどう改良したらよいのでしょうか? おそらくnCp=n*(n-1)*・・・*(n-p+1)/p!という式を使うのですがよく分かりません。よろしくお願いします。

  • C#でmCnの求め方について 階乗、組み合わせ

    C#の勉強を始めて数日の初心者です。 8個の中から4個を選ぶ組み合わせ 式1      m! mCn = ――――     n!(m -n)! 式2    m×(m-1)×(m-2)×…×(n+1) mCn = ――――――――――――       (m-n)! ---------------------------------------------- 勉強している本の中で、VBSctiptで式2を下記のような 記述例がありましたが、これをC#で書き換えると どのようになりますか? VBScriptの内容は全然分かりません。 メソッドの再帰呼び出し等は使わず、 あくまで、For文のみを使ってC#で記述したいです。 m = CInt(InputBox("何個の中から?")) n = CInt(InputBox("何個を選びますか?")) C = 1 For i = m To (n + 1) Step -1 c = c * i Next For i = (m - n) To 1 Step -1 c = c / i Next MsgBox "組み合わせ = " & CStr(c) ---------------------------------------------- 8個の中から4個を選ぶ組み合わせ 自分で書いてみたのですが、上記の式2のVBScriptの例に のっとった記述方法ではない感じがします。 int m = 8; int n = 4; int c = 0; for (int i = 1; i <= m; i++) { c = c * m; // mの階乗を求める } for (int i = 1; i <= n; i++) { d = d * n; // nの階乗を求める } e = d * d; // これの意味がよく分かりません。 f = c / e; Console.WriteLine(f); // 70 よろしくお願いします。

  • ランダウ量子力学 第二量子化(ボーズ粒子の場合)

    小教程を読んでいるのですが… ボソンの系のN個粒子の波動関数 Ψ=(N_1!…/N!)^(1/2)ΣΨ_p1(ξ1)Ψ_p2(ξ2)…Ψ_pN(ξN) について、 f^(1)_a をa番目の粒子に関する物理量演算子とした上で、全ての粒子について対称な演算子F^(1)=Σf^(1)_a の行列要素の求め方がまったく載っていません。 <N_i, N_k-1 | F | N_i-1, N_k>=f _ik√NiNk として答えが載っていますが、 Ψ_1=(N_1!…N_i-1!/N!)^(1/2)ΣΨ_p1(ξ1)Ψ_p2(ξ2)…Ψ_pN(ξN) Ψ_2=(N_1!…N_k-1!/N!)^(1/2)ΣΨ_p1(ξ1)Ψ_p2(ξ2)…Ψ_pN(ξN) として ∫Ψ*_1FΨ_2 dξ を計算すればよいのでしょうか?? それでも、とても簡単とはいかなさそうなのですが… 指針やアドバイスだけで構わないので、どなたかご教授お願いできますか。 よろしくお願いします。

  • 組み合わせ順列

    nCrを求める関数combination(n,r)をC言語で作りたいのですが、どうすればよいか教えてください。また、参考となるようなサイトを教えてください。僕の作った関数だと、すぐに桁あふれになってしまいます。そのことを考慮して、桁あふれにになりにくい関数もつくりました。これは パスカルの三角形の関係を使ったnCr=n-1Cr +n-1Cr-1の関係を使っての再帰関数です。しかし、これだと結果を出すのに時間がかかってしまいます。僕がつくった関数をいくつか出しておきますのでいい考えがあれば教えてください。64C32が高速に正確に出れれば最高です。 long combination(int n,int r) { int i, a, b; long c; if(r>n/2) r=n-r; a= n; b= 1; c= 1L; for(i=0 ;i< r ;i++){ c= c* a/ b; a--; b++; } return c; } これはすぐに桁あふれになってしまう。 long combination(int n,int r) { int i, a, b; double c; if(r>n/2) r=n-r; a= n; b= 1; c= 1.0; for(i=0 ;i< r ;i++){ c= c/b*a; a--; b++; } return (long)c; } これはcをdoubleにして計算する分、丸めこみが生じ誤差がでる。 long combination(int n,int r) { long c; if(r> n-r) r=n-r; if(r==0) return 1; else if(r==1) return n; else{ c= combination(n-1,r-1)+ combination(n-1,r); return c; } } これは誤差は出ず正確であるがいかんせん遅い!

  • 桁あふれ誤差のプログラムで質問です。

    1から15までの階乗を計算するプログラムで、階乗を求める関数を定義してその結果を確認し、その際に階乗の計算を開始する数と終了する数を記号定数で定義(#defineを使って)したいのですが、分からなくて困っています。 関数を使わないで以下のようにやり、 #include <stdio.h> #include <math.h> int main(void) { int n; int ans=1; for(n=1;n<=15;n++) { ans=ans*n; printf("%d!=%d\n",n,ans); } return 0; } これをやってみてなんとか結果が確認できたのですが、上記のように、階乗を求める関数を定義して、この上記のプログラムを書き換えて、そのときに階乗の計算を開始する数と終了する数を記号定数で定義(#defineを使って)して行いたいのですが、 分からなくて本当に困っています。助けてください。

  • 次のプログラムが作れません…

    キーボードから与えられた整数iに対して、iの階乗(i!)を求める関数"fact"を作成するというものなのですが… メイン関数は次のように与えられています。 #include<stdio.h> int fact(int); int main(void){ int in, i; printf("Input a number:"); scanf("%d",&in); for (i=1; i<=in; i++){ printf(" %d %d\n",i , fact(i) ); } return 0; } よろしくお願いします。

専門家に質問してみよう