每当一个dp问题做错了,都可以参照一下检查自己的认知。
动态规划题目性质:重叠子问题+最优子结构
递推写法是自底向上,从边界开始不断向上解决问题,直到解决了目标问题
递归写法是自顶向下,从目标问题开始,将它分解成子问题的组合,知道分解至边界为止。
贪心(类似于自顶向下): 通过策略直接选择一个子问题去求解,没被选择的子问题不去考虑
dp(从边界开始得到目标问题的解):对暂时没有被继承的子问题,后期可能会再次考虑它们。
状态的无后效性:当前状态记录了历史信息,一旦当前状态确定,就不会再改变,且未来的决策只能在已有的一个或若干个状态的基础上进行,历史信息只能通过已有的状态去影响未来的决策。(举例:每次计算dp[i],只会用到dp[i-1],不会用到dp[i-1]包含的历史信息) eg.在gymxxxB里面的dp,状态不满足无后效性这一要求
先前看多过一个说法:问什么设什么
在DAG上的dp问题中,如果用dp[i]表示以i为起点的最长路,那么需要按照逆拓扑序排序,递归满足这个,如果要输出路径,应该记录后继结点,这个很容易保证字典序最小。
边界是出度为0的点
int dp(int i)
{
if(dp[i]>0) return dp[i];
int &ans=dp[i];
for(int j=0;j<n;++j)
if(G[i][j])
{
int tmp=G[i][j]+dp(j);
if(tmp>ans)
{
ans=tmp;
choice[i]=j;
}
}
return ans;
}
void print(int i)
{
printf("%d",i);
while(choice[i]!=-1)
{
i=choice[i];
printf("->%d",i);
}
}
如果固定终点T,边界是dp[T]=0; 出度为0的点为无法到达的点-INF。再开个vis数组标明是否计算过;
背包问题:
01背包:如果是二维数组存放,v枚顺序和逆序无所谓,使用一维必须逆序
在大多数情况下,都可以把动态规划可解问题看作一个DAG,图中节点是状态,边是状态转移的方向,求解问题的顺序是按照拓扑序或者逆拓扑序。