本文一方面用于自身备考,一方面进行整理和总结注意点。
参考《算法笔记》
基本结构是:
- 类型介绍
- 程序模板
- 注意点
一、最大连续子序列和
1.类型介绍
给定一个数字序列A1,A2……An,求i,j使得Ai+……+Aj最大,输出最大和。
样例:-2 11 -4 13 -5 -2
最大和为11+(-4)+13=20
2.模板
//第一步,令dp[i]表示以A[i]为末尾的连续序列最大和
//第二步,状态转移方程
dp[0]=A[0];
for()
{
dp[i]=max(A[i],dp[i-1]+A[i]);
//只有加Ai大于Ai本身,才有往前取得必要
}
for()
{
//选出最大的和
}
3.注意点
- 动态规划要注意的问题主要就是状态转移方程,一旦确定了方程,则一切迎刃而解
- 因为这里对于每个dp只有两种情况:加上Ai大于其本身或者其本身。
- 对于动态规划要注意一般情况下前几个dp应该要进行赋值确定。
二、最长不下降子序列
1.类型介绍
一个数字序列中,找到一个最长得子序列(可以不连续),使得这个子序列是不下降(非递减)的。
样例:{1,2,3,-1,-2,7,9}
最长子序列:1,2,3,7,9,长度为5
2.模板
int ans=-1;//记录最大的dp
for(i)//遍历每一个dp
{
dp[i]=1;//边界初始条件(即先假设每个元素自成一个子序列)
for(j)//从头到i遍历,i之前的
{
//如果当前值小于之前值,并且之前值对应的dp+1大于当前值对应的dp,改变
if(A[i]>=A[j]&&(dp[j]+1>dp[i]))
{
dp[i]=dp[j]+1;//状态转移方程
}
}
ans=max(ans,dp[i]);//更新最大的dp
}
3.注意点
- 该程序实质上进行了二重遍历,外层是依次遍历Ai,内层则对Ai之前的j个值进行遍历。如果满足了条件,即将Ai接在后面能够增大长度。
- 注意根据题目逻辑分析,而不要盲目对dp的方程使用max
- 这里每个dp初始值设为1,设置边界,符合逻辑也方便比对
三、最长公共子序列
1.类型介绍
给定两个字符串A和B,求一个字符串,使得这个字符串是A和B的最长公共部分
样例:A=“sadstory” B=“adminsorry”
最长公共子序列:“adsory”
2.模板
int dp[N][N];//dp[i][j]表示A的i号位和B的j号位之前的最长公共子序列
//!!!注意,AB的读入下标要从1开始
int lenA=strlen(A+1);
int lenB=strlen(B+1);
//边界设置
//因为字符串从前向后匹配,即dp从左上角向右下角走,所以将左侧和上侧边界设0
for()
{
dp[i][0]=0;
}
for()
{
dp[0][j]=0;
}
//状态转移方程
for(i)//遍历A
{
for(j)遍历B
{
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]);
}
}
}
//答案为dp[lenA][lenB]
3.注意点
- 这道题明显的变化是,dp变成了二维数组,这是基于题目所给两个字符串考虑的。
- 这道题AB两个数组值下标从1开始,0是边界
四、最长回文子串
1、类型介绍
给出一个字符串S,求S的最长回文子串长度
样例:“PATZJUJZTACCBCC”
最长:“ATZJUJZTA”,长度9
2.模板
int dp[maxn][maxn];
memset(dp,0,sizeof(dp));
//边界初始化
for(i)
{
dp[i][i]=1;
if(S[i]==S[i+1])
{
dp[i][i+1]=1;//判断紧密相接的元素
ans=2;//初始化时注意当前最长回文子串长度
}
}
//状态转移方程
//枚举的是长度,从3开始
for(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;
}
}
}
3.注意点
- 这里不是从前往后从小到大的遍历,而是枚举长度从小到大进行遍历。
- 初始化dp[i][i]=1即本身一定与本身回文。
- 因为是从长度3开始遍历,则初始化时要考虑L=2的可能性
五、DAG最长路
一、类型介绍
给出一个有向无环图DAG,解决两个问题:
- 求整个DAG中最长路径(不固定起点和终点)
- 固定终点,求DAG的最长路径
题型1
解决办法是令dp[i]表示从i号顶点出发能够获得的最长路径长度,这样所有的dp[i]的最大值就是整个DAG的最长路径长度。
二、模板
int DP(int i){
if(dp[i]>0)return dp[i];//dp[i]已计算得到,直接返回
for(int j=0;j<n;j++) //遍历i的所有出边
{
if(G[i][j]!=INF)//如果存在出边
{
int temp = DP(j)+G[i][j]; //计算出边对应点加上ij之间的距离
if(temp>dp[i]) //如果路径更长
{
dp[i]=temp; //覆盖
choice[i]=j; //i的后继顶点是j
}
}
}
return dp[i]; //返回计算后的dp[i]
}
//需要得到最大的dp[i],然后将i作为路径起点传入
void printPath(int i)
{
printf("%d",i);
while(choice[i]!=-1)//choice数组初始化为-1
{
i=choice[i];//得到后继节点
printf("->%d",i);
}
}
三、注意点
六、背包问题
两种最简单常见的背包问题:
- 01背包问题
- 完全背包问题
1.01背包问题
01背包问题是多阶段动态规划问题,即每个阶段的状态只和上一个阶段的状态有关。
1.类型介绍
有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选择物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有一个。
2.模板
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]);
}
}
2.完全背包问题
1.n件物品,每种物品的单件重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选择物品放入背包,使得背包内物品的总价值最大。其中每种物品都有无穷件。
2.模板:
for(int i=1;i<=n;i++)
{
for(int v=w[i];v<=V;v++)
{
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
}
}