记录(干货还是有一些的) 最近三天没怎么做题(只看理论不敲代码是不行的...orz...),看了一些关于DP的文章,主要是关于背包的相关问题。
着重看了01背包和完全背包。[还捎带看了些dfs和bfs,裸题看了看,能基本复写代码,完全不会优化(记忆化算吗...嘿嘿嘿),剪枝啥的好遥远...
发现DP有三种基本的方法:1.记忆化搜索(递归)2.通过递推规律(正向、反向)来DP 3.通过状态转移来DP:
w[i+1]=F{f1(w[i]),f2(w[i]),...,fk(w[i])}; (w[i]为i时状态,fj()为对某状态的第j种决策,F{}为取不同决策结果中最优的函数(规划))
这个方程是分析DP问题的关键吗,从这个基础上可以进行优化(主要的优化是对空间的优化,因为能写出状态转移方程来的话,一般都能找到不用递归的方法,前提是不懒...,所以时间上的优化不太常见,唯一见到过的是01背包中数据限制改变导致的策略调整)空间上优化主要手段是重复利用数组(这个好像在递归调用中也可以,不过没见到相关的题就不瞎说了),例如在01背包和完全背包中dp[i][j]中因为i(不同种类物品的编号)在变化的过程中原来的不用再记录了故重新赋值给相同j的从而把二维数组压缩成一维数组,还有就是如果状态转移方程中是i到i+1这种相邻状态转移到话可以考虑区分奇偶(位运算&1即可),每次转移都释放然后重新赋值,用滚动数组来做。
就这样,总的来说没怎么刷题,以后熟练了系统整理一下DP的相关知识,在这就先不放具体的问题和代码了。
算了还是放一下吧...
重复利用数组优化空间:
#include<cstdio>
#include<algorithm>
const int M=10000;
//01背包
int dp[M+10];//dp[i+1][j]装前j种物品质量为j的物品可能的最大的价值
//v[i]、w[i]为编号为i的物品的价值量和质量,W为限重
void solve()
{
for(int i=0;i<n;i++)
for(int j=W;j>=w[i],j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d\n",dp[W]);
}
//完全背包,沿用01背包的记号,注意递推的方向是不同的
int dp[M+10];//dp[i+1][j(同01背包)装前j种物品质量为j的物品可能的最大的价值
//一开始的状态转移方程是带k的(挑选特定一种物品的个数),通过恒等变形推过来的,和01背包的思路不完全相同,时间复杂度是O(n*W)
void solve()
{
for(int i=0;i<n;i++)
for(int j=w[i],j<=W;j++)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d\n",dp[W]);
}
滚动数组
//完全背包中,利用奇偶性结合位与,使用滚动数组优化空间
int dp[2][M+10];
void solve()
{
for(int i=0;i<n;i++)
for(int j=0;j<=W;j++)
{
if(j<w[i])
dp[(i+1)&1][j]=dp[i&1][j];
else
dp[(i+1)&1][j]=max(dp[(i+1)&1][j],dp[i+1][j-w[i]]+v[i]);
}
printf("%d\n",dp[n&1][w]);
}