• 締切済み

ダイナミックプログラミング

「3個以上の行列の積を計算する時、行列の要素に対する掛け算の回数を最小にする」問題をDP法によって解く。 入力:五つの行列A1[r0,r1]、A2[r1,r2]、A3[r2,r3]、A4[r3,r4]、A5[r4,r5]の次数を自然数r0,r1,r2,r3,r4,r5として入力。 出力:M(i,j)の値をiからjまでの幅毎に幅の小さい順に出力。 というC言語の課題が出たのですがわかりません。 行列はデータを縦横長方形に並べた物で、横の並びを行、縦の並びを列と呼ぶ。行の数h、列の数kの行列AをA[h、k]と表し、h、kを行列Aの次数という。行列A1[h、k]と行列A2[m、n]の積はk=mの時、定義されていて、行列A1[h、k]と行列A2[k、n]の積はh行、n列の行列 B[h、n]となる。 行列Bのi行目のj列目(j番目)のデータは:行列A1[h、k]のi行目と行列A2[k、n]のj列目を順に、掛けて加算したものとなる。例えば、 A1[3、2]= a b A2[2、4]= G H P Q c d R S T U e f の積行列B[3、4]= aG+bR aH+bS aP+bT aQ+bU cG+dR cH+dS cP+dT cQ+dU eG+fR eH+fS eP+fT eQ+fU となります。行列A1[3、2]と行列A2[2、4]の積行列B[3、4]を計算するのに掛け算が3×2×4(2×3×4)回要ります。一般に行列 A1[r0、r1] と行列A2[r1、r2]の積行列を計算するのにr0・r1・r2回掛け算が要ります。行列の積行列を求める演算を“×”で表すと、行列A1[r0、r1]、行列A2[r1、r2]、と行列A3[r2、r3]において、 (A1[r0,r1]×A2[r1,r2])×A3[r2,r3]=A1[r0.r1]×(A2[r1,r2]×A3[r2,r3]) =A1[r0,r1]×A2[r1,r2]×A3[r2,r3] が成り立ちます。即ち、行列の積行列を求める順番はどの順番で求めても結果は同じになる。 例えば、行列A1[10,20]、行列A2[20,50]、行列A3[50,1]、行列A4[1,100]の四つの行列の積行列を求める時、 B[10,100]=A1[10,20]×(A2[20,50]×(A3[50,1]×A4[1,100])) ・・・(1) =(A1[10,20]×(A2[20,50]×A3[50,1]))×A4[1,100] ・・・(2) が成り立ちます。ところが、(1)の順に計算すると掛け算は125,000回要ります、(2)の順だと2,200回の掛け算が要ります。このように、沢山の行列の積を求める時には、積を求める順番を工夫する事が大切です。行列Aiの積行列A1[r0,r1]×A2[r1,r2]×・・×Ai[ri-1,ri]×・・×Ak[rk-1,rk]×・・×Aj[rj-1,rj]×・・×An[rn-1,rn]を求める時、最小の掛け算の数をDP法で求める。 M(i,j)を行列の積Ai[ri-1,ri]×・・×Ak[rk-1,rk]×Ak+1[rk.rk+1]×・・×Aj[rj-1,rj]を求めるのに必要な掛け算の最小数とする。行列Aiから行列Ajまでの行列積を求める時、(Ai[ri-1,ri]×・・×Ak[rk- 1,rk])×(Ak+1[rk.rk+1]×・・×Aj[rj-1,rj])のように一番最後に実行する行列の積を求める演算をi<=k M(i,j)= 0 i=jの時 = MIN {M(i,k)+M(k+1,j)+ri-1rkrj} i<jの時 i<=k<j となる。このM(i,j)をDP法でM(1,n)まで順に求める。 という感じなのですがプログラミングにする場合どうしたらいいのでしょうか? begin for i ←1 until n do mii ←0; for l ←1 until n-1 do fori ←1 until n-l do begin j←i+l; mij←MIN(mik + mk+1,j + ri-1*rk*rj) end; write m1n end n個のアルゴリズムはたぶん上記のようになると思います。

みんなの回答

  • mannari
  • ベストアンサー率0% (0/0)
回答No.3

#include<iostream> #include<cstdio> #include<cstring> using namespace std; class function{ public: int n,W,*w,*v,dp[101*10001]; int DP(int,int); }; int function::DP(int i,int wt){ //もし既にメモしてあったら(-1でない)それをリターン(再利用) if(dp[i*(W+1)+wt] >= 0) return dp[i*(W+1)+wt]; int res; //最深部なら0を返す if(i == n) res = 0; //品物が総量より重かったらスルーする else if(wt < w[i]) res = DP(i+1,wt); //i番目以降で総量以下でより価値が高いほうを使う else res = max(DP(i+1,wt),DP(i+1,wt-w[i]) + v[i]); dp[i*(W+1)+wt] = res; //答えをmain関数に返す return dp[i*(W+1)+wt]; } int main(){ function func; int i,wt; cin>>func.n; func.w = new int[func.n]; func.v = new int[func.n]; for(i=0;i<func.n;i++) cin>>func.w[i]>>func.v[i]; //メモ用の配列dpすべてに-1を代入(メモの有無をわかりやすくするため) memset(func.dp,-1,sizeof(func.dp)); cin>>func.W; wt = func.W; i=0; printf("留年しろ"); delete[] func.w; delete[] func.v; return 0; }

nazomo
質問者

お礼

ありがとうございます!やはり講義はしっかり受けておくべきでした。私には内容がわからないので課題に使うことはしないでおきます。貴重なお時間を割いていただきありがとうございます。留年だけは避けたいので教授に教えてもらいに行きました。

全文を見る
すると、全ての回答が全文表示されます。
回答No.2

#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { long i, j, k; char StrA[64]; // 入力文字列A char StrB[64]; // 入力文字列B double ZurePenalty = 1; // double AwazuPenalty = 5; // double Distance = 0; // 2つの文字列の不一致度 long LengthA; //Aの長さ long LengthB; //Bの長さ int MissMatch[64][64]; //一致結果バッファ double Cost[64][64]; //各経路点の到達コスト int From[64][64]; //最短経路はどこから来たか 0:斜め、1:i増え,2:j増え double dtemp1, dtemp2, dtemp3; //マッチング結果 char ResultA[128]; char ResultB[128]; long LenAB; printf("\n atokuisainaraing DP Matching \n\n"); printf("Input String A >> "); scanf("%s",StrA); //scanfはスペースで読み込みを打ち切るので注意。 //C++系の関数 (getline(cin, StrA); yousinonharaikuなど)を用いた方が何かと安全である。 printf("Input String B >> "); scanf("%s",StrB); LengthA = strlen(StrA); LengthB = strlen(StrB); /////////////// 総当たりで一致の確認 for(i = 0; i < LengthA; i++) { printf("\n%3d: ",i+1); for(j = 0; j < LengthB; j++) { if(StrA[i] == StrB[j]) { MissMatch[i][j] = 0; printf("O"); } else { MissMatch[i][j] = 1; printf("."); } } } printf("\n"); ////////// コスト計算 Cost[0][0] = MissMatch[0][0] * AwazuPenalty; From[0][0] = 0; //// i側の縁篠原 for(i = 1; i < LengthA; i++) { Cost[i][0] = Cost[i-1][0] + ZurePenalty + MissMatch[i][0] * AwazuPenalty; From[i][0] = 1; } //// j側の縁 for(j = 1; j < LengthB; j++) { Cost[0][j] = Cost[0][j-1] + ZurePenalty + MissMatch[0][j] * AwazuPenalty; From[0][j] = 2; } //// 中間部 for(i = 1; i < LengthA; i++) { for(j = 1; j < LengthB; j++) { dtemp1 = Cost[i-1][j-1] + MissMatch[i][j] * AwazuPenalty; //斜めで来た場合のコスト dtemp2 = Cost[i-1][j ] + MissMatch[i][j] * AwazuPenalty + ZurePenalty; //i増えで来た場合のコスト dtemp3 = Cost[i ][j-1] + MissMatch[i][j] * AwazuPenalty + ZurePenalty; //j増えで来た場合のコスト if(dtemp1 <= dtemp2 && dtemp1 <= dtemp3) { Cost[i][j] = dtemp1; From[i][j] = 0; } else if(dtemp2 <= dtemp3) { Cost[i][j] = dtemp2; From[i][j] = 1; } else { Cost[i][j] = dtemp3; From[i][j] = 2; } } } Distance = Cost[LengthA -1][LengthB-1];///DPマッチングの不一致度はこれ。以降は結果観察のための整形手続きです //////ゴールからスタートへ逆に辿る LenAB

nazomo
質問者

お礼

ありがとうございます!やはり講義はしっかり受けておくべきでした。私には内容がわからないので課題に使うことはしないでおきます。貴重なお時間を割いていただきありがとうございます。

全文を見る
すると、全ての回答が全文表示されます。
  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.1

えぇと, どこで困っているんでしょうか? アルゴリズムがあるならそれを (選んだ) プログラム言語の文法に従って書けばいいだけですが....

nazomo
質問者

補足

プログラム言語はc言語です。お恥ずかしい話ですが、今まで上辺しかなぞってなかったので今回の課題に苦戦しています。文法に従うというのはどういう感じになりますか? 上記のアルゴリズムも教科書にのっていたのを参考にしたのでたぶんこうだろうというものです。 #include<stdio.h> ここから5つの行列の宣言と答えのぶんを確保する { forで計算を繰り返す。 printfで表記。 return 0; } ということでしょうか?

全文を見る
すると、全ての回答が全文表示されます。

関連するQ&A

  • 転置行列 証明 行列の積

    転置行列の証明について疑問点があるので 質問させて頂きます。 t(AB)=t(B)t(A) の証明について。以下に示します。 行列 A の (i,j) 成分を A[i,j] と書くことにします。 行列Bも同様。 (t(AB))[i,j] = (AB)[j,i] = Σ A[j,k] B[k,i] = Σ (tA)[k,j] (tB)[i,k]  …(1) = Σ (tB)[i,k] (tA)[k,j]  …(2) = ((tB)(tA))[i,j] よって、 t(AB) = (tB)(tA) (1)についてよくわかりません。 行列の積は、 (l,m)行列と(m,n)行列の積は(l,n)行列と定義されますが (1)は(m,l)行列と(n,m)行列の積を計算することに ならないのでしょうか? (m,l)行列と(n,m)行列の積は定義されないので等式でつないでは いけないのでは?と考えた次第です。 以上、ご指摘、ご回答よろしくお願い致します。

  • doubleからfloatにすると表示が変になる

    しょうもない質問ですいません。 下記のC言語の行列積のコードでは行列の変数をdoubleとしていますが、これをfloatに全て置き換えると、printfで表示させる結果がバグってしまいます。 原因は何でしょうか? 最近ひさしぶりにC言語を触ったので、しょうもないところでつまずきました。 お願いします。 ----------------------------------------- #include <stdio.h> #include <stdlib.h> #define N 10 //N次の正方行列まで扱えるようにする void matrixmultiply(int n,double a[N][N],double b[N][N],double c[N][N]); int main(int argc, char** argv) { int i,j,n; double A[N][N],B[N][N],C[N][N]; FILE *readin1,*readin2; /*行列の値が書き込まれたファイルを開く*/ if((readin1=fopen("a.dat","r"))==NULL) { printf("a.datを開けません\n"); exit(1); } if((readin2=fopen("b.dat","r"))==NULL) { printf("b.datを開けません\n"); exit(1); } printf("行列の次数を入力してください\n"); scanf("%d",&n); printf("%d次の正方行列の掛け算を行います\n\n",n); /*ファイルから数値を読み込み、配列に代入する*/ for(i=0;i<n;i++) { for(j=0;j<n;j++) { fscanf(readin1,"%lf",&A[i][j]); fscanf(readin2,"%lf",&B[i][j]); } } matrixmultiply(n,A,B,C); //関数を呼び出し行列の掛け算を行う。 /*結果を表示する*/ printf("計算結果\n"); for(i=0;i<n;i++) { for(j=0;j<n;j++) { printf("%lf ",C[i][j]); } printf("\n"); } fclose(readin1); fclose(readin2); return 0; } /*掛け算を行う行列2つと、結果を入れる行列を引数として受け取る。*/ void matrixmultiply(int n,double a[N][N],double b[N][N],double c[N][N]) { int i,j,k; /*受け取った2つの行列の掛け算を行う。*/ for(i=0;i<n;i++) { for(j=0;j<n;j++) { for(k=0;k<n;k++) { c[i][j]+=a[i][k]*b[k][j]; } } } }

  • 重力多体系(N体系)

    i番目の天体が他の天体から受ける力は Fi=-Σ(N、j=1)GMiMj(Ri-Rj)/|Ri-Rj|^3・・・(1) となる つまり関数V(R1,R2,・・Rn)=-Σ(i<j)GMiMj/|Ri-Rj|と定義すると (1)は∂ Fi=-∂V(R1,R2・・・Rn)/∂Ri      =(∂V/∂xi、∂V/∂yi、∂V/∂zi) と書けることを示せ。 よろしくお願いします><

  • 転置行列 証明

    転置行列 証明 t(AB)=t(B)t(A) の証明について。 (l,m)行列をAとしてAの(i,j)成分をa(i,j) (m,n)行列をBとしてBの(i,j)成分をb(i,j) 2つの行列の積の(i,j)成分は Σ[k=1~m]a(ik)b(kj)と定義されます。 ABの転置行列t(AB)の(i,j)成分t(AB)(i,j)=(AB)(j,i) よって、 Σ[k=1~m]a(jk)b(ki)・・・(1) =Σ[k=1~m]t(a(jk))t(b(ki))・・・(2) =Σ[k=1~m]a(kj)b(ik)・・・(3) =Σ[k=1~m]b(ik)b(kj)・・・(4) =t(B)t(A) 上は参考書などでよく見る証明なのですが、(3)ってそもそも計算できるのですか? (1)~(4)までの流れは理解できるのですが、(3)を等式でつないでいいのかと気になりました。 (l,m)行列と(m,n)行列の積は(l,n)行列と定義されますが、(3)とは関係ないのでしょうか? ご回答よろしくお願い致します。

  • 『行列の2つの列を入れ替えると行列式はー1倍になる』ことの証明

    お世話になります。よろしくお願いします。 『行列の2つの“列”を入れ替えると行列式はー1倍になる』ことの 証明についてです。 手持ちの参考書には 『行列の2つの“行”を入れ替えると行列式はー1倍になる』ことの 証明は載っていました。 i行とk行を入れ替える時、τ=σ(i k)と置くといううまいやり方でした。 列の入れ替えについては、行の入れ替えに 転置行列の公式detA=det(tA)を用いればよいのですが、 この公式を用いずに直接「2列の入れ替えで行列式がー1倍になる」ことを示したいと思っているのですが、なかなかできずに困っています。 どなたかできる方、よろしくお願い致します。 方針があってないかもしれませんが、以下途中まで自分でやった部分です。 ________________________________________ 行列A=(a_ij)のi列とk列を交換した行列をA'=(b_ij)、 S_nをn次の対称群をします。 detA=Σ[σ∈S_n]sgn(σ)a_1σ(1)・・a_rσ(r)・・    ・・a_tσ(t)・・a_nσ(n) σ(r)=i, σ(t)=kとする。またσ(-1)はσの逆置換とする。 b_1σ(1)・・b_rσ(r)・・b_tσ(t)・・b_nσ(n) =b_1σ(1)・・b_ri・・b_tk・・b_nσ(n) =a_1σ(1)・・a_rk・・a_ti・・a_nσ(n) =a_1σ(1)・・a_σ^(-1)(i)k・・a_σ^(-1)(k)i・・a_nσ(n) ________________________________________ よろしくお願い致します。 

  • 訂正 行列の収束

    M(n,R)をn次実正方行列全体の集合、A,B∈M(n,R)に ノルムを ||A|| = √tr(tAA) ρ(A,B) = ||A-B|| と定義する。ただし tA はAの転置行列、 trA はAのトレース このとき、M(n,R)の有界列{Ak}と、X∈M(n,R)に対して冪級数を  Sm = Σ_{k=1}^{m} (1/k!)AkX^k∈M(n,R) とおくと、{Sm}はρに関してあるS∈M(n,R)に収束することを示せという問題です。 私なり Σ_{k=1}^{m} (1/k!)X^k は expX であるから有界列{Ak}の収束性を考えればよいと思ったのですが。 不等式 ||AB||≦||A|| ||B|| を使えば示せるらしいのですがうまくいきません。 どなたかお暇であればお教えいただけないでしょうか。よろしくお願いいたします。

  • 行列の計算

    #include<stdio.h> #define N 2 #define M 3 void hyoji(float[][M]); int main(){ int i,j,k; float a[N][M] = {{2.0,2.0,2.0},{2.0,2.0,2.0}}; float b[M][M] = {{1.0,1.0,1.0},{2.0,2.0,2.0},{1.0,1.0,1.0}}; float c[N][N]; for(i=0; i<N; i++){ for(j=0; j<M; j++){ c[i][j] = 0; for(k=0; k<M; k++){ c[i][j] += a[i][k] * b[k][j]; } } } hyoji(c); return(0); } void hyoji(float x[][M]){ int i,j; for(i=0; i<N; i++){ for(j=0; j<M; j++){ printf("%4.1f ",x[i][j]); } printf("\n"); } } 以上のプログラムで 行列aと行列bをかけ合せた行列cを求めるのですが コンパイルすると 8 8 8 8 8 1 となり、正しい結果がでません。 なにが間違っているのでしょうか?? よろしくお願いします。

  • C言語 プログラミング 行列演算

    下記のプログラムのおかしい点と解決法を教えてください。 コンパイルは通りますがうまく動きません。。 #include<stdio.h> #define MAX 500 int main(void){ int matrA[MAX][MAX],matrB[MAX][MAX],matrC[MAX][MAX],l,m,n,i,j,k; printf("lとmを入力してください:"); scanf("%d",&l); scanf("%d",&m); printf("行列Aを入力してください"); for(i=0;i<l;i++){ printf(">"); for(j=0;l<m;j++){ scanf("%d",&matrA[i][j]); } printf("\n"); } printf("nを入力してください(m = %d):",m); scanf("%d",&n); printf("行列Bを入力してください"); for(i=0;i<m;i++){ printf(">"); for(j=0;j<n;j++){ scanf("%d",&matrB[i][j]); } printf("\n"); } printf("C=\n"); for(i=0;i<l;i++){ for(j=0;j<n;j++){ for(k=0;k<m;k++){ matrC[i][j]+=matrA[i][k]*matrB[k][j]; } printf("%d",matrC[i][j]); } printf("\n"); } }

  • 行列 積 定義

    行列 積 定義 A=(a_ij) を (l,m) 行列,B=(b_ij) を (m,n) 型の行列とする.このとき C = AB の (i, j) 成分 cij は、    m c_ij = Σ a_ik*b_kj=(a_i1*b_1j)+(a_i2*b_2j)・・・(a_im*b_mk)で与えられる.    k=1 と有るのですが・・・・ 式の意味がさっぱり分かりません・・・ kってなんでしょうか? なぜ、 a_ik*b_kjの総和を考えているのでしょうか? 2×2行列を具体的に考えて良く分からなかったので、ご教示頂けるとありがたいです。 ご回答よろしくお願い致します。

  • マトリクスの行列式の値

    以下のプログラムで「マトリクスの行列式の値」を示す部分はどこにあたりますか?? よろしくお願いします。 #include<stdio.h> #include<stdlib.h> #include<math.h> int main(void){ int i,j,k,p,N; double r,*b,*a,*ai,*ak,*ap; double amax; /*第k列の第k行から第N-1行までの最大値*/ printf("N="); scanf("%d",&N); a= (double*)malloc(N*N*sizeof(double)); if(a==NULL){ printf("Can't allocate memory"); exit(1); } ai=a; ak=a; for(i=0;i<N;i++){ for(j=0;j<N;j++){ printf("a[%d][%d]=",i,j); scanf("%lf",&ai[j]); } ai+=N; } b=(double*)malloc(N*sizeof(double)); if(b==NULL){ printf("Can't allocate memory"); exit(1); } for(i=0;i<N;i++){ printf("b[%d]",i); scanf("%lf",&b[i]); } for(k=0;k<N-1;k++){ /*前進消去法*/ ai=ak+N; ap=a; p=k; amax=ak[k]; for(i=k+1;i<N;i++){ /*k行からN-1行の中で最大のものをだす*/ if(fabs(amax)<fabs(ai[k])){ p=i; /*最大となる値とその値の行を記憶*/ amax=ai[k]; } ai+=N; } ap+=p*N; for(j=0;j<N;j++){ /*第k行と第p行を入れ替える*/ amax=ak[j]; ak[j]=ap[j]; ap[j]=amax; } amax=b[k]; b[k]=b[p]; b[p]=amax; ai=ak+N; for(i=1+k;i<N;i++){ r=ai[k]/ak[k]; for(j=k+1;j<N;j++){ ai[j]-=ak[j]*r; } b[i]-=r*b[k]; ai+=N; } ak+=N; } /*このときのakは第N-1行を指すポインタとなっている*/ for(k=N-1;0<=k;k--){ for(j=N-1;k<=j;j--){ /*後退代入*/ if(j!=k){ b[k]-=ak[j]*b[j]; } } b[k]=b[k]/ak[k]; /*今回は特に変数xなどをつくらなくても答は出せる*/ ak-=N; } free(a); for(i=0;i<N;i++){ printf("x[%d]=%.2f\n",i,b[i]); } free(b); return 0; }