关于简单动态规划(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))。


原文地址:https://www.cnblogs.com/virtual-north-Illya/p/10045324.html