动态规划(线性、坐标型)

引入

POJ - 1163 The Triangle

在这里插入图片描述
在给定的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。(三角形的行数大于1小于等于100,数字为 0 - 99)

Sample Input

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

Sample Output

30

解题思路

D ( i , j ) D(i,j) D(i,j): 第 i 行第 j 个数字(从1开始计数)
M a x S u m ( i , j ) MaxSum(i,j) MaxSum(i,j): 从 D ( i , j ) D(i,j) D(i,j) 到底边的各条路径中,最佳路径的数字之和
问题:求解 M a x S u m ( 1 , 1 ) MaxSum(1,1) MaxSum(1,1)

解法一:暴力搜索

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int maxn=110;
int n,a[maxn][maxn];

int Solve(int i,int j){
	if(i==n)
		return a[i][j];
	else
		return max(Solve(i+1,j),Solve(i+1,j+1))+a[i][j];
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			scanf("%d",&a[i][j]);
		}
	}
	printf("%d\n",Solve(1,1));
	return 0;
} 

裸的 dfs 会深度遍历每条路径,存在着大量的重复计算,时间复杂度为 O ( 2 n ) O(2^n) O(2n),超时!!!

解法二:记忆化搜索

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int maxn=110;
int n,a[maxn][maxn],maxSum[maxn][maxn];

int Solve(int i,int j){
	if(maxSum[i][j]!=-1)
		return maxSum[i][j];
	if(i==n)
		maxSum[i][j]=a[i][j];
	else
		maxSum[i][j]=max(Solve(i+1,j),Solve(i+1,j+1))+a[i][j];
	return maxSum[i][j];
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			scanf("%d",&a[i][j]);
			maxSum[i][j]=-1;
		}
	}
	printf("%d\n",Solve(1,1));
	return 0;
} 

每算出一个 M a x S u m ( i , j ) MaxSum(i,j) MaxSum(i,j)就记录下来,下次用到时直接取用,从而避免了重复计算,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

解法三:DP+空间优化

由自上而下的递归改为自下而上的递推,并且由于是从底层一行行向上递推,故只需存储一行的 M a x S u m MaxSum MaxSum 值即可,进一步考虑,连 M a x S u m MaxSum MaxSum 数组都可以不要,直接用原数组的第 n 行代替即可,从而节省了空间。

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int maxn=110;
int n,a[maxn][maxn],*maxSum;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			scanf("%d",&a[i][j]);
		}
	}
	maxSum=a[n];
	for(int i=n-1;i>=1;i--){
		for(int j=1;j<=i;j++){
			maxSum[j]=max(maxSum[j],maxSum[j+1])+a[i][j];
		}
	}
	printf("%d\n",maxSum[1]);
	return 0;
} 

动态规划

解题步骤

  • 将原问题分解为子问题
  • 确定状态
  • 确定初始状态的值(上题中就是底边数字值)
  • 确定状态转移方程

前提

  • 问题具有最优子结构(整体最优则局部最优,反之不一定成立)
  • 重叠子问题(对于每一个子问题只求解一次,而后将其保存在一个表中,以后再遇到相同问题直接查表,从而避免了重复运算)
  • 无后效性(已经求得的状态,不受未求状态的影响)

与贪心算法的区别

  • 贪心算法:先决策再求解子问题
  • 动态规划:先求得子问题再决策

典型例题

爬台阶问题

一共有 n 级台阶,一步可以走1阶或2阶,问走到第n阶有多少种方案。
在这里插入图片描述

  • 带备忘的自顶向下法(top-down with memoization)

    • 按自然递归编写过程,保存每个子问题的解(通常保存在一个数组 或散列表中)。当需要一个子问题的解时,首先检查是否已经保存 过此解,如果是则直接返回;否则,按通常方式计算这个子问题。
  • 自底向上法(bottom-up method)

    • 一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解 都只依赖于“更小的”子问题的求解。按从小到大的顺序依次进行 求解子问题。当求解某个子问题时,它所依赖的更小的子问题都已 求解过。
      在这里插入图片描述
int f[maxn];

int solve(int n){
	if(f[n]) return f[n];
	if(n==1) return f[1]=1;
	if(n==2) return f[2]=2;
	return f[n]=solve(n-2)+solve(n-1);
}

int solve(int n){
	f[1]=1;
	f[2]=2;
	for(int i=3;i<=n;++i)
		f[i]=f[i-1]+f[i-2]
	return f[n];
}

爬台阶问题 Ⅱ

一共有 n 级台阶, 一步可以走 1, 2, 3, …, k 阶,同时有些台阶不能落脚,问走到第 n 阶的方案数。
在这里插入图片描述

最大区间和

N 个数字,每个数字∈[-1e9,1e9],选定一个区间使得和最大,求这个和。
在这里插入图片描述
在这里插入图片描述

最大区间和 Ⅱ

在这里插入图片描述

走地图

一个n×m的地图上,起点为左下角,终点为右上角,只能向右或向上行走,询问走到终点的方案数。
在这里插入图片描述

拿数问题

在这里插入图片描述

拿树问题 Ⅱ

给一个序列,里边有 n 个数,每一步能拿走一个数,比如拿第 i 个数, Ai = x,得到相应的分数 x,但拿掉这个 Ai 后,x+1 和 x-1 (如果有 Aj = x+1 或 Aj = x-1 存在) 就会变得不可拿(但是有 Aj = x 的话可以继续拿这个 x)。求最大分数。

和拿数问题Ⅰ的状态转移方程类似,不过要先进行一步转换,即将相同项合并然后从小到大进行排序,这样就只需要考虑取了 x,不能取 x+1 的情况

  • 设 dp(i) 表示以 b[i] 结尾的最大分值
  • 若 b[i] = b[i-1]+1则 dp[i]=max(dp[i-1],dp[i-2]+b[i]*mp[b[i]]);
  • 否则 dp[i]=dp[i-1]+b[i]*mp[b[i]];

东东弹钢琴(原阿里校招-3.20-B题)

在这里插入图片描述
在这里插入图片描述

矩阵选数(原阿里校招-3.25-A题)

在这里插入图片描述
在这里插入图片描述

最长上升子序列(LIS)

OpenJudge 2757

给定n 个整数 Ai, A2,…, An,按从左到右的顺序选出尽量多的整数,组成一个上升子序列。输出最长上升子序列的长度。

  • 例如,序列 1,6,2,3,7,5
  • 上升子序列可以是1,6 ;也可以是 1,2;
  • 最长上升子序列为1,2,3,5,其长度为4,故 ans= 4
    解题思路
  • 设 dp[i] 是以第 i 个数为终点的最长上升子序列
  • 初始状态 dp[1] = 1
  • dp[k] = max{ dp[i] : 1≤i<k 且 ai<ak 且 k≠1 } + 1
  • 若找不到这样的 k 则 dp[k] = 1
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
const int maxn=1010;
int n,ans,a[maxn],dp[maxn];

int main()
{
	scanf("%d",&n);
	ans=1;
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		dp[i]=1;
	}
	for(int i=2;i<=n;++i){
		for(int j=1;j<i;++j){
			if(a[i]>a[j])
				dp[i]=max(dp[i],dp[j]+1);
			ans=max(dp[i],ans);
		}
	}
	printf("%d\n",ans);
	return 0;
} 

NBUT - 1082

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它能拦截任意高度的导弹,但是每拦截一发导弹,其拦截能力就下降到只能拦截上一次拦截的导弹高度。某天,雷达捕捉到敌国的导弹来袭,导弹依次飞来,该拦截系统最多能拦截多少导弹呢?

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int maxn=1010;
int n,a[maxn],dp[maxn];

int main()
{
	while(~scanf("%d",&n)){
		if(n==0)
			break;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			dp[i]=1;
		}
		int res=0;
		for(int i=2;i<=n;i++){
			for(int j=1;j<i;j++){
				if(a[i]<=a[j]){
					dp[i]=max(dp[i],dp[j]+1);
				}
			}	
			res=max(res,dp[i]);
		}
		printf("%d\n",res);
	}	
	return 0;
}

L I S LIS LIS O ( n l o g n ) O(nlogn) O(nlogn) 的做法

  • 二分优化
  • F [ i ] F[i] F[i] 表示长度为 i i i 的最长上升子序列的末尾元素的最小值,我们发现这个数组的权值一定单调不降,于是我们按顺序枚举数组 A A A ,每一次对 F [ ] F[] F[] 数组二分查找,找到小于 A [ i ] A[i] A[i] 的最大的 F [ j ] F[j] F[j] ,并用它将 F [ j + 1 ] F[j+1] F[j+1] 更新。
  • https://www.cnblogs.com/812-xiao-wen/p/10992613.html

HDU1025

https://www.cnblogs.com/Taskr212/p/9544299.html

#include <bits/stdc++.h>
using namespace std;
const int maxn=500010;
const int inf=1e9;
int n,ans,minidx,maxidx,p,r,a[maxn],dp[maxn];
int main()
{
	int ccase=0;
	while(~scanf("%d",&n)){
		memset(a,0,sizeof(a));
		memset(dp,0,sizeof(dp));
		minidx=inf,maxidx=-inf;
		for(int i=1;i<=n;i++){
			scanf("%d%d",&p,&r);
			a[p]=r;
			minidx=min(minidx,p);
			maxidx=max(maxidx,p);
		}
		ans=1;dp[1]=a[minidx];
		for(int i=minidx+1;i<=maxidx;i++){
			if(a[i]>dp[ans]) dp[++ans]=a[i];
			else{
				int t=lower_bound(dp+1,dp+1+ans,a[i])-dp;
				dp[t]=a[i];
			}
		}
		printf("Case %d:\n",++ccase);
		if(ans==1)
			printf("My king, at most %d road can be built.\n\n",ans);
		else
			printf("My king, at most %d roads can be built.\n\n",ans);
	}
	return 0;
}

HDU1950

//HDU1950
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int k,n,ans,a[maxn],dp[maxn];
int main()
{
	scanf("%d",&k);
	while(k--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		memset(dp,0,sizeof(dp));
		dp[1]=a[1];ans=1;
		for(int i=2;i<=n;i++){
			if(a[i]>dp[ans]) dp[++ans]=a[i];
			else{
				int t=lower_bound(dp+1,dp+1+ans,a[i])-dp;
				dp[t]=a[i];
			}
		}
		printf("%d\n",ans);		
	}
	return 0;
}

输出LIS

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
const int inf=1e9+1;
ll n,p,ans,tot=1,a[maxn],dp[maxn],f[maxn],path[maxn];
int main()
{
	scanf("%lld%lld",&n,&p);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	ans=p;dp[1]=a[1];f[1]=1;
	for(int i=2;i<=n;i++){
		if(a[i]>dp[tot]){
			dp[++tot]=a[i]; 
			f[i]=tot;
		} 
		else{
			int pos=lower_bound(dp+1,dp+1+tot,a[i])-dp;
			dp[pos]=a[i];
			f[i]=pos;
		}
	}
	int t=tot,k=inf;
	for(int i=n;i>=1;i--){
		if(t<=0) break;
		if(f[i]==t&&a[i]<k){
			k=a[i];
			ans+=a[i];
			path[t--]=i;
		}
	}
	printf("%lld\n",ans);
	printf("%d\n",tot);
	for(int i=1;i<=tot;i++)
		printf("%d\n",path[i]);
	return 0;
}

最长公共子序列(LCS)

POJ 1458

给出两个字符串,求出这样的一个最长的公共子序列的长度:
子序列中的每个字符都能在两个原串中找到, 而且每个字符的先后顺序和原串中的 先后顺序一致。

解题思路

  • d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 a a a 左边 i i i 个字符形成的子串,与 b b b 左边 j j j 个字符形成的子串的 LCS 的长度
  • d p [ n ] [ 0 ] = 0 ( n = 0 … l e n 1 ) dp[n][0] = 0 ( n = 0… len1) dp[n][0]=0(n=0len1)
  • d p [ 0 ] [ n ] = 0 ( n = 0 … l e n 2 ) dp[0][n] = 0 ( n = 0… len2) dp[0][n]=0(n=0len2)
  • a [ i ] = b [ j ] a[i]=b[j] a[i]=b[j] d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 ; dp[i][j]=dp[i-1][j-1]+1; dp[i][j]=dp[i1][j1]+1;
  • 否则 d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j ] ) ; dp[i][j] = max(dp[i][j-1], dp[i-1][j]); dp[i][j]=max(dp[i][j1],dp[i1][j]);
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;

const int maxn=1010;
int dp[maxn][maxn];
char a[maxn],b[maxn];

int main()
{
	while(~scanf("%s%s",a+1,b+1)){
		int len1=strlen(a+1),len2=strlen(b+1);
		dp[0][0]=0;
		for(int i=1;i<=len1;++i)
			dp[i][0]=0;
		for(int j=1;j<=len2;++j)
			dp[0][j]=0;
		for(int i=1;i<=len1;++i){
			for(int j=1;j<=len2;++j){
				if(a[i]==b[j]){
					dp[i][j]=dp[i-1][j-1]+1;
				}
				else{
					dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
				}
			}
		}
		printf("%d\n",dp[len1][len2]);
	}
	return 0;
} 

最佳加法表达式

OpenJudge 4152

给定 n n n 1 1 1 9 9 9 的数字,要求在数字之间摆放 m m m 个加号(加号两边必须有数字),使得所得到的加法表达式的值最小,并输出该值。(要用到高精度计算,即用数组来存放 l o n g   l o n g long\ long long long 都装不下的大整数,并用模拟列竖式的办法进行大整数的加法)

解题思路

d p ( i , j ) dp(i,j) dp(i,j) 表示在 j j j 个数字中插入 i i i 个加号所能形成的表达式的最小值,那么

  • i = 0 i=0 i=0,则 d p ( i , j ) dp(i,j) dp(i,j) = j j j 个数字构成的整数
  • j < i + 1 j<i+1 j<i+1 ,则 d p ( i , j ) dp(i,j) dp(i,j) = i n f inf inf
  • d p ( i , j ) = m i n { d p ( i − 1 , k ) + n u m ( k + 1 , j ) } ( k = i . . . j − 1 ) dp(i,j) = min \{ dp(i-1,k) + num(k+1,j) \}(k=i...j-1) dp(i,j)=min{dp(i1,k)+num(k+1,j)}(k=i...j1)
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
const int maxn=60;

struct BigNum{
	int val[maxn];
	int sz;
	BigNum(){sz=0;memset(val,0,sizeof(val));}
	BigNum(string str,int from,int to){
		sz=0;
		memset(val,0,sizeof(val));
		for(int i=to;i>=from;i--){
			val[sz++]=str[i]-'0';
		}
	}
	bool operator<(const BigNum &s){
		if(sz!=s.sz){
			return sz<s.sz;
		}
		else{
			for(int i=sz-1;i>=0;i--){
				if(val[i]==s.val[i])
					continue;
				else
					return val[i]<s.val[i];
			}
		}
		return false;
	}
	BigNum operator+(const BigNum &s){
		int carry=0;
		BigNum res;
		int maxlen=max(sz,s.sz);
		for(int i=0;i<maxlen;i++){
			int cnt=val[i]+s.val[i]+carry;
			if(cnt>9){
				carry=1;
				res.val[i]=cnt-10;
			}
			else{
				carry=0;
				res.val[i]=cnt;
			}
		}
		if(carry==1){
			res.val[maxlen]=1;
			res.sz=maxlen+1;
		}
		else
			res.sz=maxlen;
		return res;
	}
	void Output(){
		for(int i=sz-1;i>=0;i--)
			cout<<val[i];
		cout<<endl;
	}
};

int m,n;
string s;
BigNum dp[maxn][maxn],num[maxn][maxn];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	while(cin>>m){
		cin>>s;
		n=s.size();
		for(int i=0;i<n;i++)
			for(int j=i;j<n;j++)
				num[i+1][j+1]=BigNum(s,i,j);			
		for(int i=0;i<n;i++)
			dp[0][i+1]=BigNum(s,0,i);			
		for(int i=1;i<=m;i++){
			for(int j=1;j<=n;j++){
				if(i+1>j)
					dp[i][j].sz=maxn;
				else{
					BigNum MinTmp;
					MinTmp.sz=maxn;
					for(int k=i;k<=j-1;k++){												
						MinTmp=(MinTmp<num[k+1][j]+dp[i-1][k])?MinTmp:num[k+1][j]+dp[i-1][k];
					}					
					dp[i][j]=MinTmp;					
				}
			}
		}	
		dp[m][n].Output();
	}
	return 0;
}

POJ - 2192 Zipper

给定三个字符串,确定是否可以通过组合前两个字符串中的字符来形成第三个字符串。前两个字符串可以任意混合,但每个字符串必须保持原来的顺序。

Sample Input

3
cat tree tcraete
cat tree catrtee
cat tree cttaree

Sample Output

Data set 1: yes
Data set 2: yes
Data set 3: no

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
using namespace std;

const int maxn=210;
char a[maxn],b[maxn],c[2*maxn];
int T,len1,len2;
bool flag,vis[maxn][maxn];

void Solve(int i,int j){
	if(i+j==len1+len2){
		flag=true;
		return;
	}
	if(vis[i][j])
		return;
	vis[i][j]=1;
	if(i<len1&&a[i]==c[i+j])
		Solve(i+1,j);
	if(j<len2&&b[j]==c[i+j])
		Solve(i,j+1);
}

int main()
{
	scanf("%d",&T);
	for(int cnt=1;cnt<=T;++cnt){
		scanf("%s%s%s",a,b,c);
		memset(vis,0,sizeof(vis));
		flag=false;
		len1=strlen(a),len2=strlen(b);
		Solve(0,0);
		printf("Data set %d: %s\n",cnt,flag?"yes":"no");
	}
	return 0;
}
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
using namespace std;

const int maxn=210;
char a[maxn],b[maxn],c[2*maxn];
int T;
bool dp[maxn][maxn];

int main()
{
	scanf("%d",&T);
	for(int cnt=1;cnt<=T;++cnt){
		scanf("%s%s%s",a,b,c);
		int len1=strlen(a),len2=strlen(b);
		memset(dp,0,sizeof(dp));
		dp[0][0]=1;
		for(int i=0;i<=len1;++i){
			for(int j=0;j<=len2;++j){
				if(i>0&&a[i-1]==c[i+j-1]&&dp[i-1][j])
					dp[i][j]=1;
				if(j>0&&b[j-1]==c[i+j-1]&&dp[i][j-1])
					dp[i][j]=1;
			}
		}
		printf("Data set %d: %s\n",cnt,dp[len1][len2]?"yes":"no");
	}
	return 0;
}

POJ1661 Help Jimmy

在这里插入图片描述
场景中包括多个长度和高度各不相同的平台。地面是最低的平台,高度为零,长度无限。

Jimmy老鼠在时刻0从高于所有平台的某处开始下落,它的下落速度始终为1米/秒。当Jimmy落到某个平台上时,游戏者选择让它向左还是向右跑,它跑动的速度也是1米/秒。当Jimmy跑到平台的边缘时,开始继续下落。Jimmy每次下落的高度不能超过MAX米,不然就会摔死,游戏也会结束。

设计一个程序,计算Jimmy到底地面时可能的最早时间。
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

struct Platform{
	int x1,x2,h;
	bool operator<(const Platform &s)const{
		return h<s.h;
	}
};

const int maxn=1010;
Platform a[maxn];
int t,n,x,y,h,h_MAX,l,r,dp[maxn][2];

int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d%d%d",&n,&x,&y,&h_MAX);
		for(int i=0;i<n;i++){
			scanf("%d%d%d",&a[i].x1,&a[i].x2,&a[i].h);
		}
		memset(dp,0x3f,sizeof(dp));
		sort(a,a+n);
		a[n].x1=x,a[n].x2=x,a[n].h=y;
		for(int i=0;i<n+1;i++){
			l=r=-1;
			for(int k=i-1;k>=0;k--){
				if(l==-1&&a[k].x1<=a[i].x1&&a[k].x2>=a[i].x1)
					l=k;
				if(r==-1&&a[k].x2>=a[i].x2&&a[k].x1<=a[i].x2)
					r=k;
			}
			if(l==-1&&a[i].h<=h_MAX)
				dp[i][0]=a[i].h;
			if(r==-1&&a[i].h<=h_MAX)
				dp[i][1]=a[i].h;
			if(l!=-1&&a[i].h-a[l].h<=h_MAX)
				dp[i][0]=a[i].h-a[l].h+min(dp[l][0]+a[i].x1-a[l].x1,dp[l][1]+a[l].x2-a[i].x1);
			if(r!=-1&&a[i].h-a[r].h<=h_MAX)
				dp[i][1]=a[i].h-a[r].h+min(dp[r][0]+a[i].x2-a[r].x1,dp[r][1]+a[r].x2-a[i].x2);
		}
		printf("%d\n",dp[n][0]);
	}
	return 0;
}

POJ1088 滑雪

Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子

01 02 03 04 05

16 17 18 19 06

15 24 25 20 07

14 23 22 21 08

13 12 11 10 09

一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-…-3-2-1更长。事实上,这是最长的一条。

//我为人人 
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int maxn=110;
int r,c,dp[maxn][maxn],h[maxn][maxn],d[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
struct node{
	int x,y,h;
	bool operator<(const node &s)const{return h<s.h;}
}a[maxn*maxn];

bool Range(int x,int y){
	if(x>=1&&x<=r&&y>=1&&y<=c)
		return true;
	return false;
}

int main()
{
	scanf("%d%d",&r,&c);
	int tot=0;
	for(int i=1;i<=r;i++){
		for(int j=1;j<=c;j++){
			scanf("%d",&h[i][j]);
			dp[i][j]=1;
			a[tot].x=i;
			a[tot].y=j;			
			a[tot++].h=h[i][j];			
		}
	}
	sort(a,a+tot);
	int ans=1;
	for(int i=0;i<tot;i++){
		int x=a[i].x,y=a[i].y;
		for(int j=0;j<4;j++){
			int dx=x+d[j][0];
			int dy=y+d[j][1];
			if(Range(dx,dy)&&h[dx][dy]>h[x][y]){
				dp[dx][dy]=max(dp[dx][dy],dp[x][y]+1);
				ans=max(ans,dp[dx][dy]);
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}

//人人为我 
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int maxn=110;
int r,c,dp[maxn][maxn],h[maxn][maxn],d[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
struct node{
	int x,y,h;
	bool operator<(const node &s)const{return h<s.h;}
}a[maxn*maxn];

bool Range(int x,int y){
	if(x>=1&&x<=r&&y>=1&&y<=c)
		return true;
	return false;
}

int main()
{
	scanf("%d%d",&r,&c);
	int tot=0;
	for(int i=1;i<=r;i++){
		for(int j=1;j<=c;j++){
			scanf("%d",&h[i][j]);
			dp[i][j]=1;
			a[tot].x=i;
			a[tot].y=j;			
			a[tot++].h=h[i][j];			
		}
	}
	sort(a,a+tot);
	int ans=1;
	for(int i=0;i<tot;i++){
		int x=a[i].x,y=a[i].y;
		for(int j=0;j<4;j++){
			int dx=x+d[j][0];
			int dy=y+d[j][1];
			if(Range(dx,dy)&&h[dx][dy]<h[x][y]){
				dp[x][y]=max(dp[x][y],dp[dx][dy]+1);
				ans=max(ans,dp[x][y]);
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}


//记忆化搜索
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int maxn=110;
int r,c,dp[maxn][maxn],h[maxn][maxn],d[4][2]={{-1,0},{1,0},{0,-1},{0,1}};

bool Range(int x,int y){
	if(x>=1&&x<=r&&y>=1&&y<=c)
		return true;
	return false;
}

int dfs(int x,int y){
	if(dp[x][y]!=0)
		return dp[x][y];
	dp[x][y]=1;
	for(int j=0;j<4;j++){
		int dx=x+d[j][0];
		int dy=y+d[j][1];
		if(Range(dx,dy)&&h[dx][dy]<h[x][y]){
			dp[x][y]=max(dp[x][y],dfs(dx,dy)+1);
		}
	}
	return dp[x][y];	
} 

int main()
{
	scanf("%d%d",&r,&c);
	int tot=0;
	for(int i=1;i<=r;i++){
		for(int j=1;j<=c;j++){
			scanf("%d",&h[i][j]);
			dp[i][j]=0;		
		}
	}
	int ans=1;
	for(int i=1;i<=r;i++){
		for(int j=1;j<=c;j++){
			ans=max(ans,dfs(i,j));
		}
	}
	printf("%d\n",ans);
	return 0;
} 

OpenJudge017 分蛋糕

有一块矩形大蛋糕,宽和高分别是整数 w 、h 。现要将其切成 m 块小蛋糕, 每个小蛋糕都必须是矩形、且宽和高均为整数。切蛋糕时,每次切一块蛋糕,将其分成两个矩形蛋糕。请计算:最后得到的 m 块小蛋糕中,最大的那块蛋糕的面积下限。
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

const int inf=1e9;
int w,h,m;
int dp[30][30][30];

int main()
{
	while(~scanf("%d%d%d",&w,&h,&m)){
		if(w==0&&h==0&&m==0)
			break;
		for(int i=0;i<=w;i++)
			for(int j=0;j<=h;j++)
				for(int k=0;k<=m;k++)
					dp[i][j][k]=(k==0)?i*j:inf;		
		for(int i=1;i<=w;i++){
			for(int j=1;j<=h;j++){
				for(int k=1;k<=min(m-1,i*j-1);k++){
					for(int p=1;p<=i-1;p++){
						for(int q=0;q<k;q++){
							dp[i][j][k]=min(dp[i][j][k],max(dp[p][j][q],dp[i-p][j][k-q-1]));
						}
					}
					for(int p=1;p<=j-1;p++){
						for(int q=0;q<k;q++){
							dp[i][j][k]=min(dp[i][j][k],max(dp[i][p][q],dp[i][j-p][k-q-1]));
						}
					}					
				} 
			}
		}
		printf("%d\n",dp[w][h][m-1]);
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值