关于简单动态规划(Dynamic Programming)的总结
Instructions
综上所述(好像没有上)我的DP真的垃圾的一批。。。
动态规划是用来避免重复计算状态导致效率低的情况,实现动规有记忆化搜索和填表两种方法,但记忆化搜索不能优化空间,所以常用的是填表法。
下面是几种非常简单的动规经典问题。
背包问题
一、0/1背包问题
每个物品只有一个,考虑选与不选,状态转移方程为f(i,j)=max(f(i-1,j),f(i-1,j-a[i])+w[i])。当然也可以用滚动数组对空间进行优化,倒着循环,状态转移方程为f(j)=max(f(j),f(j-a[i])+w[i])。
当然关于边界条件,f(i,0)=0,表示第i种物品用0个装0的价值,f(0,j)=-inf,表示不可能的情况,这种就当然不会选到了。
这个烂东西记忆化搜索才过得去。。。
二、部分背包问题
这是个贪心问题,每次选择平均价值最大的装进去。
void workk(){
for(int i=1;i<=n;i++){
thi[i].ww=(double)thi[i].w/(double)thi[i].a;
}
sort(thi+1,thi+n+1);
int x=0;
for(int i=1;i<=n;i++){
x+=thi[i].a;
if(x>k){
int y=thi[i].a-x+k;
anss+=(y*thi[i].ww);
}
else anss+=(double)thi[i].w;
if(x>=k)break;
}
}
三、多重背包
可以化成0/1背包。
四、完全背包
可以化成0/1背包,个数为k/a[i]。
五、满背包
状态转移的第二重循环多了必须要>0的限制条件,并且全部初始化为-inf,这样就不会转移到没装满的情况了。
memset(f,-inf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=c;j>=0&&j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+p[i]);
}
}
六、K背包问题
三重循环,考虑选选与不选,只是每个要选k个。
memset(f,-inf,sizeof(f));
for(int i=0;i<=20000;i++){
f2[0][i]=0;
}
for(int i=1;i<=n;i++){
for(int j=k;j>=1;j--){
for(int m=c;m>=v[i];m--)
f[j][m]=max(f[j][m],f[j-1][m-v[i]]+p[i]);
}
}
七、多费用背包
这个类型的问题有承重和体积两个限制条件,所以状态就是第i种物品在承重为k,体积为j的情况下的价值最大,用滚动数组优化空间,三重循环。
八、多背包问题
此类问题就是分别考虑第i个物品装入第一个背包和装入第二个背包的状态。
LIS和LCS
关于LIS:最长上升子序列
设置一个状态数组记录以j结尾的序列里最长上升子序列的长度,最后再取这些状态里的最大值。(n方做法)
设置一个mark数组,如果当前a[i]>mark[len],那么将mark[len+1]赋值为a[i],否则lower_bound操作返回a[i]的位置,然后把mark[len]赋值为a[i]。这样就能保证字典序最小并且len最大。(nlogn做法)
关于LCS:最长公共子序列
把S2这个序列映射成一个新序列,映射的方法是公共元素存成该元素在S1里的序号,非公共元素存成0。然后在新序列上LIS,当然,碰到0的时候跳过就行了。
序列型动态规划
一、最大连续和
设置一个状态数组表示以a[n]结尾的数的最大连续和,再取max,n方。
二、最长抖动序列
设置两个状态f,g,表示升序的抖动序列的长度和降序的抖动序列的长度。大于的元素f的转移是max(f(i-1),g(i-1)+1),小于的元素g的转移是g(i)=max(f(i-1)+1,g(i-1))。