一个问题必须拥有重叠子问题和最优子结构才能使用动态规划解决。
分治法的子问题没有重叠,但动态规划解决的问题拥有重叠的子问题;分治法不一定解决的是最优化问题,而动态规划一定解决的是最优化问题。
贪心算法只是按照一定的策略选择了一个解并得出该解作为整个问题的解,但是动态规划要选择最优的策略,然后作为整个问题的解。
最大连续子序列和
题目:给定一个数字序列A1…An,求i,j,使得Ai+…+Aj最大,输出最大和
只需要右端点的枚举
dp[i]代表以A[i]作为末尾的连续序列的最大和
状态转移方程:
dp[i]=max(A[i],dp[i-1]+A[i])
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxv=10010;
int n=0;
int A[maxv],dp[maxv]={0};
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&A[i]);
}
for(int i=1;i<=n;i++)
{
dp[i]=max(A[i],dp[i-1]+A[i]);
}
int k=1;
for(int i=2;i<=n;i++)
{
if(dp[i]>dp[k])
{
k=i;
}
}
printf("%d\n",dp[k]);
return 0;
}
状态无后效性:当前状态记录了历史信息,一旦当前状态确定,就不会再改变,且未来的决策只能在已有的一个或若干个状态的基础上进行,历史信息只能通过已有的状态去影响未来的决策
最长不下降子序列(LIS)
在一个数字序列中,找到一个最长的子序列(可以不连续),使得这个子序列是不下降(非递减)的
dp[i]代表A[0]到A[i]序列中最长不下降子序列长度
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxv=10010;
int n=0;
int A[maxv],dp[maxv]={0};
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&A[i]);
}
int ans=-1;
for(int i=1;i<=n;i++)
{
dp[i]=1;
for(int j=1;j<i;j++)
{
if(A[i]>=A[j]&&(dp[j]+1>dp[i]))
{
dp[i]=dp[j]+1;
}
}
ans=max(ans,dp[i]);
}
printf("%d",ans);
return 0;
}
最长公共子序列(LCS)
给定两个字符串(或数字序列)A和B,求一个字符串,使得这个字符串是A和B的最长公共部分(子序列可以不连续)
dp[i][j]代表A[i]和B[j]中最长公共子序列的长度
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxv=100;
int n=0;
char A[maxv],B[maxv];
int dp[maxv][maxv]={0};
int main()
{
gets(A+1);
gets(B+1);
int lenA=strlen(A+1);
int lenB=strlen(B+1);
for(int i=0;i<=lenA;i++)
{
dp[i][0]=0;
}
for(int i=0;i<=lenB;i++)
{
dp[0][i]=0;
}
for(int i=1;i<=lenA;i++)
{
for(int j=1;j<=lenB;j++)
{
if(A[i]==B[j])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
printf("%d",dp[lenA][lenB]);
return 0;
}
最长回文子串
给出一个字符串S,求S的最长回文子串的长度
dp[i][j]代表S[i]到S[j]是否为回文串,是的话为1,否的话为0
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxv=1010;
int n=0;
char S[maxv];
int dp[maxv][maxv]={0};
int main()
{
gets(S);
int len=strlen(S),ans=1;
for(int i=0;i<len;i++)
{
dp[i][i]=1;
if(i<len-1)
{
if(S[i]==S[i+1])
{
dp[i][i+1]=1;
ans=2;
}
}
}
for(int L=3;L<=len;L++)
{
for(int i=0;i+L-1<len;i++)
{
int j=i+L-1;
if(S[i]==S[j]&&dp[i+1][j-1]==1)
{
dp[i][j]=1;
ans=L;
}
}
}
printf("%d\n",ans);
return 0;
}
对于回文串问题,采用动态规划的算法解决时间复杂度为O(N2),采用hash+二分法的做法时间复杂度为O(NlogN),采用马拉车算法时间复杂度为O(N)
DAG最长路
DAG就是有向无环图
①给定一个有向无环图,怎样求解整个图的所有路径中权值之和最大的那条。
dp[i]表示从i点出发能获得的最长路径长度
#int DP(int i)
{
if(dp[i]>0)
{
return dp[i];
}
for(int j=0;j<n;j++)
{
if(G[i][j]!=INF)
{
int temp=DP(j)+G[i][j];
if(temp>dp[i])
{
dp[i]=temp;
choice[i]=j;
}
}
}
return dp[i];
}
void printPath(int i)
{
printf("%d",i);
while(choice[i]!=-1)
{
i=choice[i];
printf("->%d",i);
}
}
②固定终点,求DAG的最长路径长度
int DP(int i)
{
if(vis[i])
{
return dp[i];
}
vis[i]=true;
for(int j=0;j<n;j++)
{
if(G[i][j]!=INF)
{
dp[i]=max(dp[i],DP(j)+G[i][j]);
}
}
return dp[i];
}
经典题型:矩形嵌套问题
背包问题
01背包问题
有n件物品,重量为w[i],价值为c[i].现在有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大,每个物品只有一件
该题可以用一维的数组来存储矩阵,但是没有理解其原理,故这里只用
二维
状态矩阵的构造过程
for(int i=1;i<=n;i++)
{
for(int v=w[i];v<=V;v++)
{
dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]);
}
}
完全背包问题
有n件物品,重量为w[i],价值为c[i].现在有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大,每个物品有无穷件
边界dp[i][v]=0
for(int i=1;i<=n;i++)
{
for(int v=w[i];v<=V;v++)
{
dp[i][v]=max(dp[i-1][v],dp[i][v-w[i]]+c[i]);
}
}
总结:
当题目与序列或字符串有关,可以考虑
①令dp[i]表示以A[i]结尾或开头的xxx
②令dp[i][j]表示以A[i]至A[j]区间的xxx