AOJ0557 A First Grader

「AOJ0557 A First Grader」の編集履歴(バックアップ)一覧はこちら

AOJ0557 A First Grader」(2013/11/01 (金) 22:44:42) の最新版変更点

追加された行は緑色になります。

削除された行は赤色になります。

*AOJ0557 A First Grader **サイト [http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0557] 参考サイト [http://d.hatena.ne.jp/Respect2D/20110212/1297501157] [https://speakerdeck.com/kagamiz/aoj-0557-a-first-grader-jie-shuo] **解説 +と-の2択を選びつつ様々な制限の元ansに一致する場合数を列挙します。 2択で処理を進めるので全探索するとO(2^n)です。取りあえず再帰で全探索コードを考えてみましょう。 数列の今見ている項とそこまでの計算結果の2つの状態が必要です。 終了条件でansと計算結果が一致していれば場合の数が1つ生じます。以下のコード上では例外処理が省略されています。 >|| int rec(int index, int sum) { if(index == tail) return sum == ans; return rec(index+1, sum + seq[index+1]) + rec(index+1, sum - seq[index+1]); } ||< 同じ項を見ているときそこまでの計算結果も同じなら、その後の計算にダブりが生じます。何度も同じ計算をしないよう、計算結果の再利用をしましょう。メモ化再帰です。 >|| int rec(int index, int sum) { if(memo[index][sum] != -1) return memo[index][sum]; if(index == tail) return sum == ans; return memo[index][sum] = rec(index+1, sum + seq[index+1]) + rec(index+1, sum - seq[index+1]); } ||< これで無駄な計算が無くなり、O(ns) 100*21 = 2100通りとなりました。 再帰を考えることでindexやsumという状態を持つことが分かったので今度はDPしてみましょう。高校数学の漸化式のと同じようにDPには「初期条件」と「式そのもの」が必要です。 DPの式は以下です。 >> dp[今見ている数列のi番目][計算結果] := 場合の数 << 漸化式について帰納的に遷移を考えましょう。「計算結果」は「i-1番目の計算結果にseq[i]を足したものか、seq[i]を引いたもの」です。場合の数はその状態に辿り着くまでの経路数の和です。 初期条件を考えましょう。漸化式の計算が起こる前に予め場合の数が決まる状態が1つ存在します。場合の数は各項で+か-の二択を選択することで増加していきますが、数列の先頭ではその選択が起こりません。(「必ず+が選択される」とも言い換えられます)すなわち dp[0][seq[0]] = 1 を必ずいうことができるので、これが初期条件となります。 解答コード >|| #include <iostream> using namespace std; long long dp[101][21]; // 0で初期化済み int main() { int n, ans; int seq[100]; cin >> n; for(int i=0; i<n-1; i++) { cin >> seq[i]; } cin >> ans; dp[0][seq[0]] = 1; for(int i=1; i<n-1; i++) { for(int j=0; j<=20; j++) { if(j+seq[i]<=20) dp[i][j+seq[i]] += dp[i-1][j]; if(j-seq[i]>=0) dp[i][j-seq[i]] += dp[i-1][j]; } } cout << dp[n-2][ans] << endl; return 0; } ||<
*AOJ0557 A First Grader **サイト [http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0557] 参考サイト [http://d.hatena.ne.jp/Respect2D/20110212/1297501157] [https://speakerdeck.com/kagamiz/aoj-0557-a-first-grader-jie-shuo] **解説 +と-の2択を選びつつ様々な制限の元ansに一致する場合数を列挙します。 2択で処理を進めるので全探索するとO(2^n)です。取りあえず再帰で全探索コードを考えてみましょう。 数列の今見ている項とそこまでの計算結果の2つの状態が必要です。 終了条件でansと計算結果が一致していれば場合の数が1つ生じます。以下のコード上では例外処理が省略されています。 >|| int rec(int index, int sum) { if(index == tail) return sum == ans; return rec(index+1, sum + seq[index+1]) + rec(index+1, sum - seq[index+1]); } ||< 同じ項を見ているときそこまでの計算結果も同じなら、その後の計算にダブりが生じます。何度も同じ計算をしないよう、計算結果の再利用をしましょう。メモ化再帰です。 >|| int rec(int index, int sum) { if(memo[index][sum] != -1) return memo[index][sum]; if(index == tail) return sum == ans; return memo[index][sum] = rec(index+1, sum + seq[index+1]) + rec(index+1, sum - seq[index+1]); } ||< これで無駄な計算が無くなり、O(ns) 100*21 = 2100通りとなりました。 再帰を考えることでindexやsumという状態を持つことが分かったので今度はDPしてみましょう。高校数学の漸化式のと同じようにDPには「初期条件」と「式そのもの」が必要です。 DPの式は以下です。 >> dp[今見ている数列のi番目][計算結果] := 場合の数 << 漸化式について帰納的に遷移を考えましょう。「計算結果」は「i-1番目の計算結果にseq[i]を足したものか、seq[i]を引いたもの」です。場合の数はその状態に辿り着くまでの経路数の和です。 初期条件を考えましょう。漸化式の計算が起こる前に予め場合の数が決まる状態が1つ存在します。場合の数は各項で+か-の二択を選択することで増加していきますが、数列の先頭ではその選択が起こりません。(「必ず+が選択される」とも言い換えられます)すなわち dp[0][seq[0]] = 1 を必ず言うことができるので、これが初期条件となります。 解答コード >|| #include <iostream> using namespace std; long long dp[101][21]; // 0で初期化済み int main() { int n, ans; int seq[100]; cin >> n; for(int i=0; i<n-1; i++) { cin >> seq[i]; } cin >> ans; dp[0][seq[0]] = 1; for(int i=1; i<n-1; i++) { for(int j=0; j<=20; j++) { if(j+seq[i]<=20) dp[i][j+seq[i]] += dp[i-1][j]; if(j-seq[i]>=0) dp[i][j-seq[i]] += dp[i-1][j]; } } cout << dp[n-2][ans] << endl; return 0; } ||<

表示オプション

横に並べて表示:
変化行の前後のみ表示: