简单的动态规划练习
Posted
技术标签:
【中文标题】简单的动态规划练习【英文标题】:Simple dynamic programming exercise [closed] 【发布时间】:2014-08-29 22:50:36 【问题描述】:让我们有一个(给定尺寸的)小方块的字段,每个方块都有一个值。从每个方格,一个人只能移动到正下方的方格,或者对角线向左或向右的那个方格。任务是找到穿越该领域的最大组合价值。
例如对于输入
1
6 5
3 1 7 4 2
2 1 3 1 1
1 2 2 1 8
2 2 1 5 3
2 1 4 4 4
5 2 7 5 1
输出应该是 32,但我的代码输出 20。
我的方法是通过以下方式详尽地尝试通过该领域的所有可能路线:
y == last_row return value[x,y]
f(x,y)
y != last_row return value[x,y] + max(f(x-1,y+1),f(x,y+1),f(x+1,y+1))
我的方法、我的代码或两者都有错误吗?
代码在这里:
#include <iostream>
#include <vector>
#include <limits>
using namespace std;
typedef int T;
T max(T x, T y, T z)
if(x < y)
if(y < z) return z;
else return y;
else
if(y > z) return x;
else
if(x > z) return x;
else return z;
//Finds the maximum amount of stones possibly gathered by following coordinates x,y
//The topmost left is (0,0), bottom right is (columns-1,rows-1)
T max_stones_found_following(T x, T y, vector< vector<T> > A)
//Reached the last row?
if(y == A.size()-1) return A[x][y];
else
T went_left, went_right, went_down;
if(x-1 >= 0) went_left = max_stones_found_following(x-1, y+1, A);
else went_left = numeric_limits<T>::min();
if(x+1 <= A[x].size()-1) went_right = max_stones_found_following(x+1, y+1, A);
else went_right = numeric_limits<T>::min();
went_down = max_stones_found_following(x, y+1, A);
return A[x][y] + max(went_left, went_right, went_down);
int main()
//Initialization
T test_cases, rows, columns, stones_found, max_stones;
vector< vector<T> > A;
cin >> test_cases;
while(test_cases--)
//Field input
cin >> rows >> columns;
for(int i = 0; i < rows; i++)
vector<T> row;
for(int j = 0; j < columns; j++)
T in;
cin >> in;
row.push_back(in);
A.push_back(row);
max_stones = 0;
stones_found = 0;
//Try starting at different positions in the first row
for(int i = 0; i < columns; i++)
stones_found = max_stones_found_following(i, 0, A);
if(stones_found > max_stones) max_stones = stones_found;
//Output
cout << max_stones << endl;
return 0;
【问题讨论】:
您的解决方案似乎没有使用动态规划。 这个练习的“动态”部分在哪里?我看到一个 typedefT
并且没有任何模板(除了使用向量的向量)。告诉你的导师他们对“动态”的定义......不是。
@WhozCraig:“动态编程”是系统工程中的一个主题,与动态类型或动态内存管理无关。我绝对不会使用vector<int>
以外的模板来解决这个问题。
@BenVoigt 那么不同的思想和感知流派,同意了。除非得到指示,否则我也不会这样做(这里很可能就是这种情况,但很难说)。
【参考方案1】:
你的一些问题:
方法max
比需要的更复杂。您正在进行多次比较以找到最大值。见下文。
您的主要问题是使用i
和j
倒置,根据调用站点i
表示column
在row 0
和方法max_stones_found_following
中的开始位置值矩阵的行。
固定代码(顺便说一下,对于大输入数据来说,这是一个非常慢的解决方案,而不是动态编程):
#include <iostream>
#include <vector>
#include <limits>
using namespace std;
typedef int T;
T max(T x, T y, T z)
return std::max(x, std::max(y, z));
// Finds the maximum amount of stones possibly gathered by following coordinates
// x,y
// The topmost left is (0,0), bottom right is (columns-1,rows-1)
T max_stones_found_following(T x, T y, vector<vector<T>> A)
// Reached the last row?
if (y == A.size() - 1)
return A[y][x];
else
T went_left, went_right, went_down;
if (x - 1 >= 0)
went_left = max_stones_found_following(x - 1, y + 1, A);
else
went_left = numeric_limits<T>::min();
if (x + 1 <= A[y].size() - 1)
went_right = max_stones_found_following(x + 1, y + 1, A);
else
went_right = numeric_limits<T>::min();
went_down = max_stones_found_following(x, y + 1, A);
return A[y][x] + max(went_left, went_right, went_down);
int main()
// Initialization
T test_cases, rows, columns, stones_found, max_stones;
vector<vector<T>> A;
cin >> test_cases;
while (test_cases--)
// Field input
cin >> rows >> columns;
for (int i = 0; i < rows; i++)
vector<T> row;
for (int j = 0; j < columns; j++)
T in;
cin >> in;
row.push_back(in);
A.push_back(row);
max_stones = 0;
stones_found = 0;
// Try starting at different positions in the first row
for (int i = 0; i < columns; i++)
stones_found = max_stones_found_following(i, 0, A);
if (stones_found > max_stones)
max_stones = stones_found;
// Output
cout << max_stones << endl;
return 0;
参见dynamic programming 的定义。适用于解决以下问题:
可以分解成子问题。 而且这个子问题有些重叠。例如:这个问题可以分为子问题,从row 0
-> row i
的最佳路径是什么。考虑到这一点,到row i
的最佳路径问题仅取决于到row i-1
的最佳路径和ith
行的矩阵值。使用它,您可以将解决方案扩展到row i
,直到到达最后一行。
在最后一行将是最好的路径,直到该行的每一列,搜索这个的最大值。
源代码(动态编程):
#include <algorithm>
#include <iostream>
#include <vector>
typedef std::vector<int> row_t;
typedef std::vector<row_t> matrix_t;
int main()
// Initialization
int test_cases, rows, columns;
matrix_t A;
std::cin >> test_cases;
while (test_cases--)
std::cin >> rows >> columns;
for (int i = 0; i < rows; i++)
row_t row(columns);
int in;
for (int j = 0; j < columns; j++)
std::cin >> in;
row[j] = in;
A.push_back(row);
// Dynamic Programming Here
// For storage the best path until each cell
matrix_t best_A (rows, row_t(columns, 0));
std::copy(A[0].cbegin(), A[0].cend(), best_A[0].begin());
for (int i = 1; i < rows; i++)
for (int j = 0; j < columns; j++)
// right down
if (j > 0 && best_A[i - 1][j - 1] + A[i][j] > best_A[i][j])
best_A[i][j] = best_A[i - 1][j - 1] + A[i][j];
// left down
if (j < columns - 1 && best_A[i - 1][j + 1] + A[i][j] > best_A[i][j])
best_A[i][j] = best_A[i - 1][j + 1] + A[i][j];
// down
if (best_A[i - 1][j] + A[i][j] > best_A[i][j])
best_A[i][j] = best_A[i - 1][j] + A[i][j];
// End Dynamic Programming
auto it = std::max_element(best_A[best_A.size() - 1].cbegin(), best_A[best_A.size() - 1].cend());
// Output
std::cout << *it << std::endl;
return 0;
如前所述,您可以计算到 row i
的最佳路径,只读取第一行 i
行,您可以即时进行(读取时,读取第一行,计算最佳起始位置,读取第二行,计算直到第二行的每一列的最佳路径,依此类推),如果输入非常非常大,这非常好。你也不需要保存最佳路径直到rows 1..i
,你只需要计算last row
和计算actual row
的最佳路径。
【讨论】:
不错的答案。但是,对于某些输入情况,您的代码会出现运行时错误。【参考方案2】:动态编程是解决这个问题的好方法。但是就像匿名评论一样,您没有使用它,或者至少没有以明确的方式使用它。
如果您有C
列,那么您有C
可能的起始位置和C
第二个位置,但有3*C - 2
对(第一,第二)。利用动态规划的方法是注意马尔可夫性质,对于第二行中的每个单元格,在以该单元格结尾的所有路径中,只保留得分最高的那个。
然后,对于每一行,您再次评估 3*C - 2
路径,只保留其中的 C
。
重复直到到达底部。
在实现方面,您应该有一个指向当前行的C
“最佳”路径向量,并构建指向下一行的C
最佳路径向量。然后下一行成为当前行(使用vector::swap
)。每个“路径”必须至少存储累积值,但存储访问过的位置的历史记录也可能很好。
事实上,您甚至不需要将整个网格存储在内存中。您可以在阅读时对每一行执行所有处理。
注意:这里使用动态编程会使复杂度 R*C
而不是 C * 3^R
想出一个真正的解决方案实际上很有趣。警告:前方指路!
#include <iostream>
#include <sstream>
#include <vector>
void solve_one_case();
int main(int argc, char** argv)
/* driver */
const std::string input = "6 5\n"
"3 1 7 4 2\n"
"2 1 3 1 1\n"
"1 2 2 1 8\n"
"2 2 1 5 3\n"
"2 1 4 4 4\n"
"5 2 7 5 1";
std::stringbuf inputstream(input, std::ios_base::in);
auto const oldbuf = std::cin.rdbuf();
std::cin.rdbuf(&inputstream);
solve_one_case();
std::cin.rdbuf(oldbuf);
return 0;
void solve_one_case()
/* get board size from input */
int rows = 1, columns = 1;
std::cin >> rows >> columns;
std::vector<char> route(rows * columns, '|');
/* get first row from input */
std::vector<int> current_row, prev_row;
current_row.resize(columns);
for( int& start_score : current_row )
std::cin >> start_score;
/* get all cells from input, solving */
char* pRoute = &route[columns];
for( int row = 1; row < rows; ++row )
prev_row = current_row;
int cell = 0;;
for( int column = 0; column < columns; ++column )
std::cin >> cell;
if (column > 0 && prev_row[column-1] > current_row[column])
current_row[column] = prev_row[column-1];
*pRoute = '\\';
if (column + 1 < columns && prev_row[column+1] > current_row[column])
current_row[column] = prev_row[column+1];
*pRoute = '/';
current_row[column] += cell;
++pRoute;
/* find best value in final row */
int best_score = current_row[0], best_end = 0;
for( int i = 1; i < columns; ++i )
if (best_score < current_row[i])
best_score = current_row[i];
best_end = i;
std::cout << "Best score is " << best_score << "\n";
/* backtrack along route */
int route_column = best_end;
for( int row = 0; row < rows; ++row )
char breadcrumb = '*';
pRoute -= columns;
std::swap(pRoute[route_column], breadcrumb);
switch (breadcrumb)
case '/': ++route_column; break;
case '\\': --route_column; break;
/* print routes */
pRoute = &route[0];
for( int row = 0; row < rows; ++row )
std::cout.write(pRoute, columns);
pRoute += columns;
std::cout << '\n';
std::cout << std::flush;
输出:
Best score is 32
||*||
|/|*\
//|\*
/||*|
||*|\
|/*||
【讨论】:
如果我使用|
作为路径标记,回溯部分可能会变得更短,因为这些是连续的 ASCII 值。以上是关于简单的动态规划练习的主要内容,如果未能解决你的问题,请参考以下文章