問題のリンク- http://www.iarcs.org.in/zco2013/index.php/problems/ROUNDTABLE
キャッスルキャメロットでのディナータイムです。恐ろしいラウンドテーブルの騎士たちはデザートを求めています。あなた、シェフはスープにいます。アーサー王を含むN人の騎士がいて、それぞれデザートの好みが異なりますが、すべてのデザートを作る余裕はありません。
ラウンドテーブルなので、各ナイトの優先デザートの製造コストが与えられます。リストはアーサー王のデザートのコストから始まり、反時計回りに進みます。
あなたが作る最も安いデザートを選ぶことに決めました、その結果、隣接する騎士のすべてのペアのために、少なくとも1人は彼のデザートを得ます。これは騎士が抗議しないことを保証します。この条件での今夜のディナーの最低費用はいくらですか?
ダイナミックプログラミングアプローチを使用して、i-1
とi-2
の最小のものを考慮し、次のコードを考え出しました
#include<cstdio>
#include<algorithm>
using namespace std;
int main() {
int n,i,j,c,f;
scanf("%d",&n);
int k[n],m[n][2];
for(i=0;i<n;++i) scanf("%d",&k[i]);
m[0][0]=k[0]; m[0][1]=0;
m[1][0]=k[1]; m[1][1]=1;
for(i=2;i<n;++i) {
c=1000;
for(j=i-2;j<i;++j) {
if(m[j][0]<c) { c=m[j][0]; f=m[j][1];}
}
m[i][0]=c+k[i]; m[i][1]=f;
}
if(m[n-2][0]<m[n-1][0] && m[n-2][1]==0) printf("%d\n",m[n-2][0]);
else printf("%d\n",m[n-1][0]);
}
m
配列の2番目の次元を使用して、特定のシーケンスが開始された騎士(1番目または2番目)を格納しました。 m[n-2]<m[n-1]
の場合のためにこれを実行する必要がありましたが、シーケンスはナイト2から開始しました。デザートがないと、2つの隣接するナイトが作成されるためです。テーブルの丸い形状が原因で問題が発生します。
ケースを検討すると、異常が発生します-2 1 1 2 1 2
。プログラムは、1番、3番、5番の騎士を選択することにより、答えが4であるはずのときに5と答えます。この時点で、最初のアルゴリズム(アプローチ)自体を疑い始めました!
どこで私は間違えましたか?
コードでのcとfの使用法を理解できません。それについて少し説明していただければ幸いです。また、私はすべてのiに対してi-2とi-1を繰り返す必要を見つけません。 (私はまだ投稿にコメントすることができません)今私はあなたにあなたのアプローチと同様にこの問題を解決するための私のアルゴリズムを与えます。
Dp [n] [2]を維持します。 dp [i] [0]は0から始まるケースで、dp [i] [1]は1から始まるケースです。各iについて、dp [i] [0]番目の要素をdp [として取得できます。 i-2] [0] + k [i]およびdp [i] [1]はdp [i-2] [1] + k [i]として。トリックは、適切な場合にのみこれを行うことです。ここで、i%2が0に等しい場合、それらは0から始めて残りの要素が他の場合に適用されるときにカバーされるものであると想定できます。コーナーケースは、テーブルが円形であるため、0から始まる場合の最後の要素を回避することです。
これが私の作業コードです。これは、あなたが今述べたケースで機能します。サイトのメンバーではないため、他のケースをテストすることはできません。
#include<vector>
#include<iostream>
#include<cmath>
using namespace std;
int main() {
int n, i, j, temp;
vector<int> k;
cin>>n;
for(i=0;i<n;i++) {
cin>>temp;
k.Push_back(temp);
}
int dp[n][2];
/* 0 will contain the case of starting from 0
* 1 will contain the case of starting from 1 */
dp[0][0] = k[0];
dp[0][1] = 0;
dp[1][0] = k[0]; // assign the previous value itself
dp[1][1] = k[1];
for(i=2;i<n;i++) {
if(i%2 == 0) {
// case of strting from 0 .
dp[i][0] = dp[i-2][0] + k[i];
dp[i][1] = dp[i-1][1];
}
else{ // case of starting from 1
dp[i][0] = dp[i-1][0];
dp[i][1] = dp[i-2][1] + k[i];
}
}
cout<<min(dp[n-1][0], dp[n-1][1]);
}
テーブルが円形であるため、アルゴリズムが機能しないと思います。しかし、別の動的プログラミング手法を使用してこれを解決できます。
配列 'C []'に、各騎士のデザートのコストを格納します。次の2つの点を考慮する必要があります。
テーブルがしばらくの間円形であることを忘れましょう。各反復で前進しているので、_W [i]は「i」の左隣を含めるコストのみを格納します。
特定の騎士「k」について、すでに計算されたW [k-1]と_W [k-1]の値を使用して、一定の時間でW [k]と_W [k]を計算できます。
騎士 'k'を含めない場合は、騎士 'k-1'を含めて、少なくとも1人が彼のデザートを手に入れるようにします。したがって、_W [k]は騎士 'k-1'を含めるコスト、つまりW [k-1]と等しくなります。
ナイト「k」を含めることを選択した場合、「k」と「k-1」の両方を含めるか、「k-1」ではなく「k」を含めるかの2つの選択肢があります。 2つの最小値を使用します。
今私たちは持っています:
したがって、_W [0]およびW [0]の値がわかっている場合は、Nまでのすべての騎士のWおよび_W値を計算できます。
W [0]がC [0]と等しくなることはわかっています。ナイト0を含まない場合のコストである_W [0]は、元の問題に戻るとC [N-1]であるその左隣を含む場合のコストになります。
しかし、W [1]が_W [0] + C [1]として選択され、_W [0]がC [N-1]である場合はどうなりますか?騎士N-1を2度考えるかもしれません。
この問題を克服するために、C [N-1] <C [0]であるかどうかを確認します。そうである場合は、N-1が選択されることがわかっているため、Nを1つ減らし、最後の騎士を削除します。
最終的な答えは、W [N-1]と_W [N-1]の最小値になります。
疑似コードが必要な場合:
W[0] = C[0]
_W[0] = C[N-1]
if C[N-1] < C[0]:
then N = N-1
for i from 1 to N-1:
W[i] = C[i] + min(W[i-1],_W[i-1])
_W[i] = W[i-1]
return min(W[N-1],_W[N-1])
各反復で一定の時間を実行する場合、時間の複雑さはO(N)であり、問題の時間制限内で適切に実行されます。
だから私はあなたのコードを見てきました、そして私は正直でなければなりません、あなたの心とコードに入るのにかなり長くかかります、そして私が喜んで入れます。しかし、私は別の方法を提案できます理解し、書くのが簡単になると思うサブ問題を分割します。
4x(n + 1)配列を定義します。インデックスi、jでは、jインデックスは最初のnナイトの副問題に対応します。最後のスポットは最初に繰り返されたスポットです。 4つの配列の場所は4つの異なるケースに対応し、最初の騎士がデザートを受け取ったかどうか、j番目の騎士がデザートを受け取ったかどうかに対応します。
最初の騎士がデザートを受け取るかどうかを制限しているため、ベースケースは簡単です。そのため、通常よりも簡単です。以前のケースに関して現在のケースを解決することはかなり簡単です。最初の騎士がデザートを受け取るか、受け取らないケースは完全に分離しています。いずれの場合も、現在のナイトがデザートを取得する場合の値は、前のエントリの最小値に、現在のデザートのコストを加えたものです。彼がそうしないケースは、最後の騎士がデザートを得た前の解決策と、デザートの現在のコストの合計です。
アルゴリズムをまとめます。4つの行のうち、2つだけが関連します。これは、開始と終了(繰り返される開始)が一致する場合に対応するのは2つだけだからです。これら2つのうち最小のものを選択します。