一个月前写的,勉强上传存档……
第一次接触DP
POJ 1163
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
当时并没有DP概念,正向思维,把每一层的所有状态全都算出来,再给下一层。题目是不难,但这个图对我的启示挺大。
其中只有根是已知的,每个状态的转移方式也是已知的。
从根到叶是正向思维。顺次可以得到所有的儿子的状态。当所有状态数量是有限的时候,正向的规模被限制,可以尝试求解。
但有时只需要求其中一个儿子的状态,如果还是从根节点出发一一求出状态则显得有点浪费。所以此时应该逆向思维。
当上一层对下一层状态如树状(图左),则非常理想,从儿子看,他只需要知道父亲的状态,而不需知道伯伯的状态,依次到达根节点。但当状态有交叉时(图右),则要求出相关联的伯伯的状态。逆向求解有助于减少运算的规模。
背包——DP中的经典
背包系列问题是DP入门的经典。一言以蔽之:
n
件物品,第i
件体积为v[i]
, 价值为w[i]
, 件数为k[i]
Fi(j):前i个物品放进容积为j的容器里的最大价值(逐步达到最大)
0-1背包
所有k[i]均为1
Fi(j)={Fi−1(j)max{Fi−1(j),Fi−1(j−Vi)+Wi}Vi>j>=0C>=j>=Vi
for(int i = 0;i < n;i++)
for(int j = C;j >= v[i];j--)
dp[j] = max(dp[j], dp[j-v[i]]+w[i]);
完全背包
所有k[i]=inf
Fi(j)={Fi−1(j)max{Fi−1(j),Fi(j−Vi)+Wi}Vi>j>=0C>=j>=Vi
for(int i = 0;i < n;i++)
for(int j = v[i];j <= C;j++)
dp[j] = max(dp[j], dp[j-v[i]]+w[i]);
多重背包
k[i]>=1
- 化为0-1背包,则共有 ∑ki 件物品,当物品件数多时可能会超时。
- 二进制拆分。将k拆为 1,2,4,...,2p,k−2p+1+1(>0,p为最大的满足该式的值) .可知它们可以组合出在 1到k 范围内的所有数字。则只要将一件物品拆分成这样的p+2件物品,化为0-1背包。注意每件物品重量、价值等比例扩大。
for(int i = 0;i < n;i++)
{
int m = 1;
while(k[i] > 0)
{
if(m > k[i]) m = k[i];
k[i] -= m;
for(int j = C;j >= v[i];j--)
dp[j] = max(dp[j], dp[j-v[i]]);
m <<= 1;
}
}
3.单调队列优化