グリッドアクセスの動的計画法

パーソナルソリューションの記録

グリッドアクセスの動的計画法

リソース制限
時間制限:1.0秒メモリ制限:256.0MB
問題の説明
  N N(N <= 10)のグリッドグラフ



があり、いくつかの正方形を正の整数で埋め、他の正方形を数字の0に入れます。   誰かが図の左上隅のポイントA(1,1)から開始し、右下隅のポイントB(N、N)に到達するまで下または右に歩くことができます。彼が歩く途中で、彼は正方形の数字を取ることができます(それを取った後の正方形は数字0になります)。   この人は、ポイントAからポイントBまで2回歩きます。取得した数の合計が最大になるように、そのようなパスを2つ見つけてください。入力形式   の最初の行は整数N(N Nのグリッドを表す)であり、後続の各行には3つの整数があります。最初の2つは位置を表し、3番目の数値はその位置に配置された数値です。0の1行は、入力の終了を示します。
出力フォーマット
  は、2つのパスで取得された最大の合計を表す整数を出力するだけで済みます。
サンプル入力
8
2 3 13
2 6 6
3 5 7
4 4 14
10 2 21
5 6 4
6 3 15
7 2 14
0 0
サンプル出力
67

質問の解釈は、
  N * Nグリッドから取得され、左上隅(1,1)から始まり、右下隅(N * N)に到達します。歩行方向は右または下のみで、通過グリッドの数字は削除されます。得られた数の合計を最大化するために2つのパスを見つけます。


  問題を解決するという考えは迷路に似ています。最初の反応はdfsです。すべての道路を横断しますが、検索のコストが高すぎます。問題の目的は、値の合計が最大の2つを見つけることです。 。
  次の図に示すように、最初に「合計を最大化するにはどうすればよいか」を考えます。

  到達し(2,2)た最大パスの合計がであることがグラフで簡単にわかりますが、それは5なぜ5ですか?方向は右または下にしか行けないので、そこに到達するのは(2,2)上または左からでしかありません。したがって、パスのどちら側が最大値であるかを比較するだけで済みます。
  これには动态规划解決すべき問題があるように思われます唯一の問題は、今言うことですつまり、そのようなパスを尋ねたが、グリッドの数は一度だけ取ることができる2つの質問を見つけることですA路径した後(i,j)(i,j)値がA削除され、値はグリッド上になるとなり0B路径直後に再び1つ取った0この問題には2つの解決策があります。
解決策1
質問で与えられたサンプルを例として取り上げます。
ここに画像の説明を挿入します
  ポイントAからポイントBに移動するには、4次元配列を使用して2人のAとBが同時にポイントAから開始します。dp [i] [j] [h] [k]、(I、j)は、グリッド上のAの対応する位置を表し、(h、k)は、グリッド上のBの対応する位置を表します。
dp [i] [j] [h] [k]の位置に到達するための4つの状況があります

1. Aは上から、Bは上から(上と上)
2。Aは上から、Bは左から(左上)
3。Aは左から、Bは上から(左上)
4。 Aは左から、Bは左から正方形(左左)

  4種類の状況の値を比較する必要があるだけで、最大のものを取ります。さらに重要な点として、位置の値は1回しか取得できません。その後、AとBが同じ位置に移動すると、グリッド上の値のみを追加する必要があります。異なる位置にある場合、それらの2つの位置の値を追加する必要があります。


このようにして、漸化式を構築することができます

  • AとBが同じ位置にある場合i==hj==k

dp[i][j][h][k] = max(dp[i-1][j][h-1][k], dp[i-1][j][h][k-1], dp[i][j-1][h-1][k], dp[i][j-1][h][k-1]) + value[i][j]

  • AとBが異なる位置にある場合i!=hj!=k

dp[i][j][h][k] = max(dp[i-1][j][h-1][k], dp[i-1][j][h][k-1], dp[i][j-1][h-1][k], dp[i][j-1][h][k-1]) + value[i][j] + value[h][k]
元の問題の解決策dp [n] [n] [n] [n]
ソースコード:

#include<iostream>
#include<algorithm>
using namespace std;

int dp[11][11][11][11]={
    
    0};

int main()
{
    
    
    int n;
    int value[11][11]={
    
    0};
    int a, b, c;
    cin >>n;
    while(1)
    {
    
    
        cin >>a>>b>>c;
        value[a][b]=c;
        if(a==0&&b==0&&c==0)    break;
    }
    for(int i=1;i<=n;i++)
    {
    
    
        for(int j=1;j<=n;j++)
        {
    
    
            for(int h=1;h<=n;h++)
            {
    
    
                for(int k=1;k<=n;k++)
                {
    
    
                    if(i==h&&j==k)
                    {
    
    
                        dp[i][j][h][k]=max(dp[i-1][j][h-1][k], dp[i-1][j][h][k-1]);
                        dp[i][j][h][k]=max(dp[i][j][h][k], dp[i][j-1][h-1][k]);
                        dp[i][j][h][k]=max(dp[i][j][h][k], dp[i][j-1][h][k-1]);
                        dp[i][j][h][k] += value[i][j];
                    }else{
    
    
                        dp[i][j][h][k]=max(dp[i-1][j][h-1][k], dp[i-1][j][h][k-1]);
                        dp[i][j][h][k]=max(dp[i][j][h][k], dp[i][j-1][h-1][k]);
                        dp[i][j][h][k]=max(dp[i][j][h][k], dp[i][j-1][h][k-1]);
                        dp[i][j][h][k] += (value[i][j]+value[h][k]);
                    }
                }
            }
        }
    }
    cout << dp[n][n][n][n] << endl;
    return 0;
}

スキーム2
  最初のスキームは同時に歩くことであり、2番目のスキームは別々に歩くことです。Aは最初に歩き、合計が最大のパスを見つけてから、Bに歩かせます。この場合、配列は2次元で定義する必要があるだけです。
再帰:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + value[i][j]
  今の質問は、「Aがどちらの方向に進んだかをどうやって知るのですか?」です。
  (i、j)グリッドには上または左からしか到達できないことは誰もが知っているので、パスの出所を記録することをお勧めします。つまり、route [] []配列を定義するだけで済み、route [i] [j]は、到着グリッド(i、j)が上から来るか左から来るかを示し、次のように指定されます。U 上からの手段、使用L左からの意味。
  (i、j)は(i-1、j) 来て、route [i] [j]に録音してください U
ここに画像の説明を挿入します
  図の例から、(2,2)への最大パス合計は12であり、パスは(1,1)-(1,2)-(2,2)からであり、次にroute [ 2] [2] = 'U'。(n、n)に達したら、最初の旅行の最大パス合計を記録し、次にroute [n] [n]から始めて、このパスを見つけ、パス上のすべての値を0に割り当てます。次に、2回目の旅行を行い、2回の旅行の解決策を足して答えを得ます。
ソースコード:

#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
    
    
    int N;
    int ans;
    int value[11][11]={
    
    0};
    int dp[11][11]={
    
    0};
    char route[11][11];
    cin >> N;
    for(;;)
    {
    
    
        int a,b,c;
        cin >> a >> b >> c;
        if(a==0&&b==0&&c==0)    break;
        value[a][b]=c;
    }

    // 走第一躺
    for(int i=1;i<=N;i++)
    {
    
    
        for(int j=1;j<=N;j++)
        {
    
    
            if(j==1){
    
           // 当位置在第一列,只能从上方来
                dp[i][j] = dp[i-1][j] + value[i][j];
                route[i][j] = 'U';
            }else if(i==1){
    
         // 当位置在第一行,只能从左方来
                dp[i][j] = dp[i][j-1] + value[i][j];
                route[i][j] = 'L';
            }
            else if(dp[i-1][j]>=dp[i][j-1]){
    
    
                dp[i][j] = dp[i-1][j] + value[i][j];
                route[i][j] = 'U';
            }else if(dp[i-1][j]<dp[i][j-1]){
    
    
                dp[i][j] = dp[i][j-1] + value[i][j];
                route[i][j] = 'L';
            }
        }

    }
    ans = dp[N][N];     // 记录第一躺的解

    int x=N;    // 行号,当来自上方,行号减一
    int y=N;    // 列号,当来自左方,列好减一
    while(x>=1&&y>=1)
    {
    
    
        if(route[x][y]=='U')  x--;
        else if(route[x][y]=='L') y--;
        value[x][y]=0;      // 将经过的格子全赋值为0
    }

    // 走第二趟
    dp[11][11] = {
    
    0};
    for(int i=1;i<=N;i++)
    {
    
    
        for(int j=1;j<=N;j++)
        {
    
    
            if(j==1){
    
    
                dp[i][j] = dp[i-1][j] + value[i][j];
            }else if(i==1){
    
    
                dp[i][j] = dp[i][j-1] + value[i][j];
            }
            else if(dp[i-1][j]>=dp[i][j-1]){
    
    
                dp[i][j] = dp[i-1][j] + value[i][j];
            }else if(dp[i-1][j]<dp[i][j-1]){
    
    
                dp[i][j] = dp[i][j-1] + value[i][j];
            }
        }

    }
    cout << ans+dp[N][N] << endl;       // 两趟的结果相加
    return 0;
}

何かおかしいことがあれば、ポインタを教えてください

おすすめ

転載: blog.csdn.net/biu_527/article/details/114955574