问题描述:
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更长。事实上,这是最长的一条。
Input
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
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
分析及代码实现:
这个问题应该来说是个简单的,很容易想到用动态规划去做的题目。这个问题满足最有子结构
是比较容易看出来。非常容易建立如下递归式:
如果从i,j可以顺着某侧滑的话:
dis_sk[i][j] = max{dis_sk[i-1][j],dis_sk[i][j-1],dis_sk[i+1][j],dis_sk[i][j+1]}+1
那么我们很容易写出递归的:
int dis(int i,int j){
for(i,j上侧,下侧,左侧,右侧)
if(该位置没有越界){
if(顺着该侧可以往下滑)
如果该侧位置可以滑行的距离(递归调用dis函数)大于dis_sk[i][j],则把dis_sk[i][j]改成该距离+1
}
}
把这个递归改成动态规划很容易,只要在开始判断一下
if(dis_sk[i][j]) return dis_sk[i][j]; //dis_sk[i][j]开始为0
这样基本上就可以很顺畅的写出代码了:
定义的变量如下:
一个用来判断是否越界的辅助函数:
下边就是以lookup方式写的动态规划实现的从i,j下滑最大距离:
最后是main函数,取dis(i,j)的最大者就是所求的最长区域的长度:
#include <iostream>
#include <string>
#include <tr1/unordered_map>
using namespace std;
int h[101][101];
int dis_sk[101][101];
int pre[101][101][2];//用来记录结点的前一个结点
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
int r,c;
bool in_bound(int i,int j){
return i>=0&&i<r&&j>=0&&j<c;
}
int dis(int k,int m){
int i,temp;
if(dis_sk[k][m])
return dis_sk[k][m];
for(i=0;i<4;i++){
if(in_bound(k+dx[i],m+dy[i]))
{
if(h[k][m]>h[k+dx[i]][m+dy[i]]){
temp=dis(k+dx[i],m+dy[i]);
if(dis_sk[k][m]<=temp) {
pre[k][m][0]=k+dx[i];
pre[k][m][1]=m+dy[i];
dis_sk[k][m]=temp+1;
}//如果儿子结点记录的路径大于或等于父结点,那么儿子结点就是父亲结点的下一个结点
//dis_sk[k][m]=dis_sk[k][m]>temp?dis_sk[k][m]:temp+1;
}
}
}
return dis_sk[k][m];
}
int main(){
int max_dis=0;
int temp;
int i,j;
cin>>r>>c;
for(i=0;i<r;i++){
for(j=0;j<c;j++)
{
cin>>h[i][j];
dis_sk[i][j]=0;
pre[i][j][0]=-1;
pre[i][j][1]=-1;
}
}
int ii,jj;
for(i=0;i<r;i++){
for(j=0;j<c;j++)
{temp=dis(i,j);
if(max_dis<temp){
ii=i;//记录最大路径的结点序号
jj=j;//记录最大路径的结点序号
max_dis=temp;
}
// max_dis=max_dis>temp?max_dis:temp;
}
}
cout<<max_dis+1<<endl;//最长路径
//输出其路径
while((pre[ii][jj][0]!=-1)&&(pre[ii][jj][1]!=-1))
{
cout<<h[ii][jj]<<" ";
int temp_i=pre[ii][jj][0];
int temp_j=pre[ii][jj][1];
ii=temp_i;
jj=temp_j;
}
cout<<h[ii][jj]<<" "<<endl;
return 0;
}
回溯法
八皇后
这种可能成为n的指数形式的编程一般用 回溯的方法找其中一个解,如果要全部求出解那和递归效率差不多,不过递归有个压栈,也就可能栈被压爆了,那还是用回溯来弄,回溯的另一种改进方法时 分支限定法:也就是在他用回溯求出一个解后,那这个解就可以成为一个限定条件来进行其它解的最优情况的排除,就可以尽量少的进行遍历了。
#include<stdio.h>
#include<math.h>
int x[100];
bool place(int k)//考察皇后k放置在x[k]列是否发生冲突
{
int i;
for(i=1;i<k;i++)
if(x[k]==x[i]||abs(k-i)==abs(x[k]-x[i]))
return false;
return true;
}
void queue(int n)
{
int i,k;
for(i=1;i<=n;i++)
x[i]=0;
k=1;
while(k>=1)
{
x[k]=x[k]+1; //在下一列放置第k个皇后
while(x[k]<=n&&!place(k))
x[k]=x[k]+1;//搜索下一列
if(x[k]<=n&&k==n)//得到一个输出
{
for(i=1;i<=n;i++)
printf("%d ",x[i]);
printf("\n");
//return;//若return则只求出其中一种解,若不return则可以继续回溯,求出全部的可能的解
}
else if(x[k]<=n&&k<n)
k=k+1;//放置下一个皇后
else
{
x[k]=0;//重置x[k],回溯
k=k-1;
}
}
}
void main()
{
int n;
printf("输入皇后个数n:\n");
scanf("%d",&n);
queue(n);
}
递归实现
#include <iostream>
#include <cmath>
using namespace std;
int n=4;
int x[100];
bool place(int k)//考察皇后k放置在x[k]列是否发生冲突
{
int i;
for(i=1;i<k;i++)
if(x[k]==x[i]||abs(k-i)==abs(x[k]-x[i]))
return false;
return true;
}
void Backtree(int k){
if(k>n){
for(int i=1;i<=n;i++)
cout<<x[i];
cout<<endl;
return;
}
for(int i=1;i<=n;i++)
{ x[k]=i;
if(place(k)) Backtree(k+1);
}
}
int main(){
Backtree(1);
return 0;
}