dp:动态规划算法
它针对满足特定条件的一类问题,对各状态维度进行分阶段、有顺序、无重复、决策性的遍历求解。
dp解题的一般思路:
- 把原问题分解为子问题
- 确定状态
- 确定一些初始状态(边界状态)的值
- 确定状态转移方程
能用dp解决的问题的特点:
- 问题具有最优子结构性质
- 无后效性
- 子问题可重叠性
思路:
1.找子问题
该题可转化为求以ak(k=1,2,3…N)为终点的最长上升子序列的长度
2.确定状态
子问题只和一个变量——数字的位置相关。因此序列中数的位置k就是“状态”,而状态k对应的“值”,就是以ak作为“终点”的最长上升子序列的长度。则状态一共有N个。
3.找出状态转移方程
maxLen(k)表示以ak作为“终点”的最长上升子序列的长度那么:
初始状态:maxLen(1)=1
maxLen(k)=max{ maxLen(i) : 1 <=i <k 且 a~i~ < a~k~ 且 k!=1 } + 1
若是找不到这样的i,则maxLen(k)=1
例题代码如下:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=1010;
int maxLen[MAXN];
int a[MAXN];
int main()
{
for(int i=1;i<=10;i++)
{
cin>>a[i];
maxLen[i]=1; //初始化
}
for(int i=2;i<=10;i++)
{
//每次求第i个数为终点的最长上升子序列的长度
for(int j=1;j<i;j++)
{
//查看第j个数为终点的最长上升子序列
if(a[i]>a[j])
maxLen[i]=max(maxLen[i],maxLen[j]+1);
}
}
cout<<*max_element(maxLen+1,maxLen+10+1);
//该函数在maxLen[1]到maxLen[10]之间寻找最大值
return 0;
} //时间复杂度是O(n^2)
此附max_element函数用法:max_element函数
DP的常用两种形式
- 递归型
优点:直观,容易编写
缺点:可能会因递归层数太深导致爆栈,函数调用带来额外的时间开销。无法使用滚动数组节省空间。总体来说,比递推型慢。 - 递推型
效率高,有可能使用滚动数组节省空间。
例题:公共子序列
给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。
Sample Input
abcfbc abfcab
programming contest
abcd mnp
Sample Output
4
2
0
解题思路:
输入两个字符串s1,s2.
设MaxLen(i,j)表示:
s1的前i个字符形成的子串和s2的前j个字符形成的字串的最长公共子序列的长度(i,j从1开始算)
MaxLen(i,j)就是本题的“状态”。
题目就是要求求出 MaxLen(strlen(s1),strlen(s2))。
显然:
MaxLen(n,0) = 0
MaxLen(0,n) = 0
递推公式:
if(s1[i]==s2[j])
MaxLen(i,j)=MaxLen(i-1,j-1)+1;
else
MaxLen(i.j)=Max( MaxLen(i,j-1),MaxLen(i-1,j) );
//时间复杂度是O(mn), m,n是两个字符串长度。
A - Common Subsequence (其实就是公共子序列问题)
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
int f[1100][1100];
char s1[1000], s2[1000];
int len1, len2;
int main()
{
while(scanf("%s %s", s1, s2) != EOF)
{
len1 = strlen(s1);
len2 = strlen(s2);
memset(f, 0, sizeof(f));
for(int i = 1;i <= len1; ++i)
{
for(int j = 1;j <= len2; ++j)
{
if(s1[i-1] == s2[j-1]) f[i][j] = f[i-1][j-1]+1;
else
{
f[i][j] = max(f[i-1][j], f[i][j-1]);
}
}
}
cout << f[len1][len2] << endl;
}
return 0;
}
B - Super Jumping! Jumping! Jumping! 即最大上升子段和
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1e5+10;
#define inf 0x3f3f3f3f;
int n;
int dp[MAXN];
int main()
{
while(scanf("%d",&n)!=EOF&&n)
{
int a[MAXN]={0};
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
dp[0]=0;
for(int i=1;i<=n;i++)
{
dp[i]=-inf;
for(int j=0;j<i;j++)
{
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]);
}
dp[i]+=a[i];//记录i点前的最大子段和
}
int ans=-inf;
for(int i=1;i<=n;i++)
{
if(dp[i]>ans)
ans=dp[i];
}
cout<<ans<<endl;
}
return 0;
}
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
int max(int x,int y)
{
if(x>y) return x;
return y;
}
int mp[110][110];
int dp[110][110];
int maxp=0;
int maxi,maxj;
int dx[]={0,0,0,1,-1}; //横坐标加减
int dy[]={0,1,-1,0,0}; //纵坐标加减
int r,c;
int dfs(int x,int y) //递归深度搜索
{
if(dp[x][y]!=0)
{
return dp[x][y];
}
for(int i=1;i<=4;i++)
{
if(mp[x][y]>mp[x+dx[i]][y+dy[i]]&&x+dx[i]>=1&&x+dx[i]<=r&&y+dy[i]>=1&&y+dy[i]<=c)
{
dp[x][y]=max(dp[x][y],dfs(x+dx[i],y+dy[i])+1);
}
}
return dp[x][y]; //指坐标(x,y)的点能滑的最长长度
}
int main()
{
scanf("%d %d",&r,&c);
memset(dp,0,sizeof dp); //将dp数组初始化为0
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
scanf("%d",&mp[i][j]); //输入各个点的高度
}
}
int max1=0;
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
max1=max(max1,dfs(i,j));
}
}
printf("%d\n",max1+1);
return 0;
}