描述
Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。
输入
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
输出
输出最长区域的长度。
样例输入
5 5 1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9
样例输出
25
1.动态规划:
所谓动态规划,就是一个最优化问题,先将问题分解为子问题,并且对于这些分解的子问题自身就是最优的才能在这个基础上得出我们要解决的问题的最优方案,要不然的话就能找到一个更优的解来替代这个解,得出新的最优自问题,这当然是和前提是矛盾的。动态规划不同于贪心算法,因为贪心算法是从局部最优来解决问题,而动态规划是全局最优的。用动态规划的时候不可能在子问题还没有得到最优解的情况下就做出决策,而是必须等待子问题得到了最优解之后才对当下的情况做出决策,所以往往动态规划都可以用 一个或多个递归式来描述。而贪心算法却是先做出一个决策,然后在去解决子问题。这就是贪心和动态规划的不同。
一般遇到一个动态规划类型的问题,都先要确定最优子结构,还有重叠子问题,这两个是动态规划最大的特征,然后就是要写 动态规划的状态方程,这个步骤十分十分的重要的,写动归方程是需要一定的经验的,这可以通过训练来达到目的。接着就是要自底向上的求解问题的,先将最小规模的子问题的最优解求出,一般都用一张表来记录下求得的解,到后来遇到同样的子问题的时候就可以直接查表得到答案,最后就是通过一步一步的迭代得出最后问题的答案了。
我的理解最重要的东西就是一定会要一个数组或者其他的存储结构存储得到的子问题的解。这样就可以省很多时间,也就是典型的空间换时间
这道题可以类比为求一个最长上升(下降)子序列。用动态规划的思路来解决的话,首先我们要将问题分解成一个个与原问题形式相同的子问题。比如在最长上升子序列中,我们将“求整个序列的最长上升子序列”这个问题分解为了多个规模更小的子问题:“求以该序列中某个数字为结尾的最长上升子序列”,那么有了这么一个递推的思路,再从前往后一层层根据递推公式递推,问题便迎刃而解。本题也是一样。这道题可以分解成“求每一个点的最长上升子序列”,这就是本题的状态。接下来我们自底而上地求每一个点的最长上升子序列,然后在他们之中寻找最大的那个值,便是我们要找的答案。、
第一步:先进行点的递增排序。
第二步:以已排序序列作为操作顺序,依次求出每一个点的最长上升子序列并保存,以减少后续计算,这是典型的空间换时间。
第三步:遍历完所有点后,找到其最大值并输出。
当然,对以上算法的代码实现,依然有难点存在。譬如:若是要求每个点的最长上升子序列,这就要求我们要去对每个点的上下左右四个点进行计算,这也就产生了非常多的特殊情况,光是四个位于边角的点就要我们写四种不同的代码,否则就会出错。这极大地增加了我们的工作量,成堆的if语句会导致我们在某些细节上处理出错或不到位的概率大大增加。对于这个问题,我要说的是:计算机永远比人精确。与其我们自己分类讨论,不如把这项工作交给计算机自己完成。我们对每一个点依然会去比较上下左右四个点,那么这四个点中有哪些不在我们的允许的区间内,由计算机来判断。这样我们只需要写一个if语句就可以完成上述一堆if语句才能完成的工作。
上代码:
#include
#include
#include
using namespace std;
int map[105][105];
int maxL[105][105];
int r, c;
struct point
{
int x, y;
int height;
point() {}
point(int _x, int _y, int h) :x(_x), y(_y), height(h) {}
};
bool cmp(point a, point b)
{
return a.height < b.height;
}
bool check(int x, int y)
{
if (x >= 1 && x <= r && y >= 1 && y <= c)
return true;
else return false;
}
int ans = 1;
int to[4][4] = { {1,0},{0,1},{-1,0},{0,-1} };
inline void DP(point &a)
{
int x, y;
x = a.x, y = a.y;
int x1, y1;
for (int i = 0;i < 4;i++)
{
x1 = x + to[i][0];
y1 = y + to[i][1];
if (check(x1, y1))
{
if (map[x][y] > map[x1][y1])
maxL[x][y] = max(maxL[x][y], maxL[x1][y1] + 1);
ans = max(ans, maxL[x][y]);
}
}
}
int main()
{
cin >> r >> c;
vector v;
for (int i = 1;i <= r;i++)
{
for (int j = 1;j <= c;j++)
{
cin >> map[i][j];
v.push_back(point(i, j, map[i][j]));
maxL[i][j] = 1;
}
}
sort(v.begin(), v.end(), cmp);
for (int i = 0;i < v.size();i++)
{
DP(v[i]);
}
cout << ans;
return 0;
}
还有几个值得说明的点:其一是传自定义类型的变量时最好是传引用,其二则是一个我不明白的点:最后若是把输出的ans换成
*max_element(&maxL[1][1],&maxL[r + 1][c + 1])
可以过,但是把后面两个+1去掉的话,这道题就过不了了,可按理来说maxL[r + 1][c + 1]位置应该没有有用的值才对啊......