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

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

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

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

アルゴリズム

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

最適化

最適化とはメソッドやデザインの最適な処理方法を選択することです。パフォーマンスの向上を目指す為に行われます。プログラミングにおける最適化は、アルゴリズムのスピードアップや、要求されるリソースを減らすことなどを指します。

Q&A

解決済

3回答

2787閲覧

組み合わせの計算の高速化

grape_ll

総合スコア83

C

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

アルゴリズム

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

最適化

最適化とはメソッドやデザインの最適な処理方法を選択することです。パフォーマンスの向上を目指す為に行われます。プログラミングにおける最適化は、アルゴリズムのスピードアップや、要求されるリソースを減らすことなどを指します。

0グッド

0クリップ

投稿2020/08/22 11:51

下記のリンクの問題を解いていたのですが,sampleケースが両方通って他のケースは一つがWA,残りはTLEとなってしまいました.while文も終了条件はきちんとできていますし,for文もそこまで計算量があるようには思えません.あるとしたら組み合わせを求めているところかなと思うのですが,ここはどのようにしたら高速化できますでしょうか.
問題

C

1#include<stdio.h> 2#include<stdlib.h> 3#include<string.h> 4#include<math.h> 5 6double nPm(int n,int m){ 7 if(m<=1) return (double)n; 8 return (n-m+1)*(nPm(n,m-1)); 9} 10 11double nCm(int n,int m){ 12 return nPm(n,m)/nPm(m,m); 13} 14 15 16int main(void){ 17 int N,M; 18 int *r; 19 20 scanf("%d %d",&N,&M); 21 22 r=(int *)malloc(sizeof(int)*M); 23 24 for(int i=0;i<M;i++) scanf("%d",&r[i]); 25 26 double p=1/(double)M; 27 28 double P=1; 29 30 double LOG10=N*log10(p); 31 32 //printf("p=%f\n",p); 33 34 int A=N; 35 for(int i=0;i<M;i++){ 36 P=nCm(A,r[i]); 37 LOG10+=log10(P); 38 A-=r[i]; 39 } 40 41 //printf("P=%f\n",P); 42 43 int ans=0; 44 double divide=1.0; 45 while(1){ 46 if(LOG10>=log10(divide)) break; 47 divide/=10; 48 ans++; 49 } 50 51 printf("%d\n",ans); 52 return 0; 53}

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

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

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

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

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

swordone

2020/08/22 18:58 編集

間違った指摘でした。削除します。
guest

回答3

0

swordoneさんの回答は、次のように修正すればいいということですよね。

diff

1 double nPm(int n,int m){ 2- if(m<=1) return (double)n; 3- return (n-m+1)*(nPm(n,m-1)); 4+ if(m<=1) return log10(n); 5+ return log10(n-m+1) + nPm(n,m-1); 6 } 7 8 double nCm(int n,int m){ 9- return nPm(n,m)/nPm(m,m); 10+ return nPm(n,m) - nPm(m,m); 11 } 12 for(int i=0;i<M;i++){ 13 P=nCm(A,r[i]); 14- LOG10+=log10(P); 15+ LOG10 += P; 16 A-=r[i]; 17 }

追記
選ばれなかった色がある場合、すなわち r[i] = 0 の場合、
double nPm(int n,int m) の m が 0 になりますよ。
次のような修正が必要なのではありませんか?

C

1double nPm(int n,int m){ 2 if(m==0) return 0; 3 if(m<=1) return log10(n); 4 return log10(n-m+1) + nPm(n,m-1); 5}

投稿2020/08/22 23:40

編集2020/08/22 23:49
kazuma-s

総合スコア8224

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

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

kazuma-s

2020/08/23 05:36

お見事です。 次の 2個所を修正すればよいでしょう。 - logs=(double *)malloc(sizeof(double)*N); + logs = (double *)malloc(sizeof(double)*(N + 1)); - LOG10 -= -N*log10(M); + LOG10 -= N*log10(M);
swordone

2020/08/23 05:45

これは私の回答へのコメントでしょうか? ご指摘ありがとうございます。修正しました。
kazuma-s

2020/08/23 05:50

すみません。コメントを書くところを間違えました。
guest

0

ベストアンサー

常用対数で計算するなら、全部常用対数で計算すればいいのでは?
階乗が絡んでくると、掛け算ではちょっと数が大きくなればすぐオーバーフローしますよ。

条件を満たす投票パターンは「同じものを含む順列」の公式から
N!/(r1!r2!...rM!)通りとなるので、
これを全投票パターンのM^N通りで割れば確率pになります。
p≧10^(-x) ⇔ log10(p)≧-x ⇔ -log10(p)≦xなので、
pの常用対数を取って、-1倍して天井関数を取れば、条件を満たす最小の整数xが求められます。
pの常用対数の計算において、対数の性質から
log10(p)=log10(N!)-{log10(r1!)+log10(r2!)+...+log10(rM!)}-N
log10(M)
であり、階乗の定義と対数の性質から自然数nに対し、
log10(n!)=log10(1)+log10(2)+...+log10(n)
なので、掛け算を使うことなく計算が可能です。
log10(N!)はループで計算することになりますが、その過程でN以下のすべての自然数nに対するlog10(n!)の値が出てくることになるので、それを配列に保存しておけば、log10(ri!)に再利用できます。
なお、0!=1に注意してください。

Cのコードは書いたことがないのですが、たぶんこんな感じ?
ポインタなども理解が十分でないため、見様見真似で書いてます。

C

1int main(void){ 2 int N,M; 3 double *logs; 4 5 scanf("%d %d",&N,&M); 6 7 logs=(double *)malloc(sizeof(double)*(N+1)); 8 logs[0]=logs[1]=0.0; //log10(0!)=log10(1!)=0 9 10 double LOG10 = 0.0; 11 12 for(int i=2;i<=N;i++){ //log10(1)=0なのでスキップして2から 13 LOG10+=log10(i); 14 logs[i] = LOG10; 15 } 16 17 int r; 18 for(int i=0;i<M;i++) { 19 scanf("%d",&r); 20 LOG10 -= logs[r]; 21 } 22 23 LOG10 -= N*log10(M); 24 int ans = (int)ceil(-LOG10); 25 26 printf("%d\n",ans); 27 return 0; 28}

投稿2020/08/22 18:28

編集2020/08/23 05:44
swordone

総合スコア20651

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

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

grape_ll

2020/08/23 11:18

確かにこの考え方なら常用対数の加法減法で表すことができるうえ,計算量も削減できるように思えます. また,慣れない言語であるのに参考コードを書いてくださりありがとうございます.きれいに問題が解決しました.
guest

0

nCmの値を愚直に計算すると倍精度でも表せません
C言語でこういう値がどう扱われるのかわかりませんが、おそらくそのせいでLOG10がNaNか何かになってbreakされず無限ループになってるんでしょう。

投稿2020/08/22 14:04

yudedako67

総合スコア2047

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

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

grape_ll

2020/08/23 11:07

組み合わせを使用すると精度が足りないということは知りませんでした. ですが,私の考えでは計算の過程にどうしても組み合わせを用いる必要が出てきてしまいます.なにか他のうまい方法はありますでしょうか.
swordone

2020/08/23 12:45

たぶんn,mが大きくなるとnPm(n,m)もnPm(m,m)もINFINITYになると思います。 INFINITY同士の割り算だから、NaNになるのでしょう。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問