引入
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=0…len1)
- d p [ 0 ] [ n ] = 0 ( n = 0 … l e n 2 ) dp[0][n] = 0 ( n = 0… len2) dp[0][n]=0(n=0…len2)
- 若 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[i−1][j−1]+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][j−1],dp[i−1][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(i−1,k)+num(k+1,j)}(k=i...j−1)
#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;
}