题目链接:http://poj.org/problem?id=1088
题目内容:
Description
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
Input
Output
Sample Input
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
Sample Output
25
Source
遇到这样的题目,先大概估算一下时间,假如用暴力搜索,对每个点都dfs一次,显然在行数和列数最大为100的情况下,必然超时。那么不妨看看用动态规划,应该怎么解决:
假设dp(i,j)以点(i,j)为起点的最长路径的长度,那么dp(i,j)等于什么呢,根据题意,某个点可以转移到另一个点,当且仅当另一个点是其上下左右的相邻点,而且高度减小。
那么可以得出dp(i,j)==max(dp(i',j')+1),(i',j')是符合题意的(i,j)的相邻点。
而答案就是所有dp(i,j)中的最大值。
1、记忆化搜索
暴力搜索的缺点在于做了大量的重复的工作,比如在求dp(0,0)的时候,假设(0,0)可以转移到(0,1),那么在求出dp(0,0)的时候,dp(0,1)必然被求了出来,当下次某个点会转移到(0,1)时,又要重新求一次dp(0,1)。
动态规划的一个思想就是,用空间换时间,既然我在转移的过程中,把某个点(i,j)的dp(i,j)给求了出来,那么为了避免下次其他点转移到这个点又要重复工作,干脆就把已经求出来的dp(i,j)的值记录下来,那么下次再遇到的时候,直接查表。这样,可以保证每个点(i,j)都只求一次dp(i,j)。
附上AC代码
#include
#include
#include
#define width 105
using namespace std;
int maps[width][width];//存地图的高度
int dp[width][width];//记录dp(i,j)
int r,c;//行数和列数
int dfs(int x, int y)//深度优先搜索求解dp(x,y)
{
if(dp[x][y]!=0)//当dp(x,y)!=0,表示该点(x,y)的dp(x,y)已经被求过了
{
return dp[x][y];//直接返回dp(x,y)
}
else
{
dp[x][y]=1;//某点(x,y)的dp(x,y)至少为1
for(int i=-1;i<=1;i++)
{
for(int j=-1;j<=1;j++)
{
if(i==0&&j!=0||i!=0&&j==0)//遍历四个相邻点
{
int newx=x+i;
int newy=y+j;
if(newx>=0&&newx=0&&newy>r>>c;
for(int i=0;i>maps[i][j];
}
}
memset(dp,0,sizeof(dp));
for(int i=0;ians)
{
ans=dp[i][j];//记录已经求出的所有的dp(i,j)的最大值
}
}
}
cout<
那么如果用递推而不是递归,又该怎么办呢? 看下面一个例子
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=17
单调递增最长子序列
描述
求一个字符串的最长递增子序列的长度3
aaa
ababc
abklmncdefg
1
3
7
Longest Increasing Subsequence单调递增最长子序列,很经典的一道动态规划题目
如果用动态规划解决,那么应该很容易想到:
假设dp(i)表示以i点为终点的最长递增子序列长度,显然dp(i)=max(dp(j)+1),j
那么从哪里开始递推呢?显然dp(1)=1,第一个点作为终点,它的最长递增子序列只有它这个点。
AC代码如下:
#include
#include
#include
using namespace std;
int dp[10005];
int main()
{
int n;
int ans=1;
cin>>n;
while(n--)
{
string s;
cin>>s;
ans=1;
memset(dp,0,sizeof(dp));
dp[0]=1;//第一个点的dp值为1
for(int i=1;is[j])//j点在i点之前,而且j点的值小于i点
{
dp[i]=max(dp[j]+1,dp[i]);//这就是我上面说的dp(i)=max(dp(j)+1)
}
if(dp[i]>ans)//用ans记录已经求出的dp(i)的最大值
{
ans=dp[i];
}
}
}
cout<
同样的 本题和最长递增子序列是类似的,只是从一维变成了二维,那么完全可以把二维的降为一维的,降维之后,给每个点带上二维的坐标信息,就可以用上面的方法解决。
降维之后,要按点的高度给一维数组排个序,因为点在二维数组中可以向相邻点转移,你无法知道在一维数组中哪个点在哪个点的前面,一个技巧是给一维数组按点高度从小到大排序,这样后面的点一定可以由前面的点转移而来,因为前面点的高度比后面的点要低,然后判断前面的点是否是后面点的相邻点,是的话就可以转移。
AC代码如下:
#include
#include
#define row 105
#define length 10005
using namespace std;
struct node//用一维数组去存储矩阵,带上二维数组的坐标信息
{
int x;
int y;
int dp;//该点(x,y)的dp(x,y)的值
int value;//该点的高度
}maps[length];
bool cmp(const node& a, const node& b)
{
if(a.value>r>>c;
for(int i=0;i>maps[index].value;
maps[index].x=i;
maps[index].y=j;
maps[index].dp=1;//初始的时候,每个点的最长路径dp(i,j)都为1
index++;
}
}
sort(maps,maps+index,cmp);
for(int i=1;imaps[i].dp)//相当于我说的dp(i,j)=max(dp(i',j')+1),只是这里变成一维
{
maps[i].dp=maps[j].dp+1;
}
if(maps[i].dp>ans)//记录已经求过的dp(i)的最大值
{
ans=maps[i].dp;
}
}
}
}
cout<