记忆化搜索--(01背包,完全背包,最长公共子序列,最长上升子序列)

记忆化动态规划学习笔记

1.记忆化搜索与动态规划

01背包问题1

n的物品都有自己的wi,vi,背包最多可装载的重量为W,背包里的物体价值最大(1<<n<<100,1<=W<=10000,1<=wi,vi<=100
暴力写法,存在较多的重复计算,复杂度O(pow(2,k))
从第i个物品挑选总重小于j的部分

int rec(int i, int j) {
	int res;
	if (i == n)  res = 0;//无剩余
	else if (j < w[i])res = rec(i + 1, j);//不够
	else res = max(rec(i + 1, j), rec(i + 1, j - w[i])+v[i]);
	return res;
}

记忆化搜索,复杂度O(nw)nw为参数组合数量

int sovle(int i, int j) {
	//记忆化保存
	if (dp[i][j] > 0)return dp[i][j];
	int res;
	if (i == n)res = 0;
	else if (j < w[i])res = rec(i + 1, j);
	else res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
	return dp[i][j] = res;
}

定义:dp[i+1][j]:从0到i(i+1个)物品中选出总重量不超过j的最大物品的价值和。(假设物体从第0个开始)

    1.dp[0][j]=0   (dp[i][0]=0如果物品价值都大于0)
    2.dp[i+1][j]=dp[i][j]  (j<w[i]) 
      dp[i+1][j]=max{dp[i][j],dp[i][j-w[i]]+v[i]}

写法如下:i逆向进行也可以,此处不写

void solve() {
	for(int i=0;i<n;i++)
		for (int j = 0; j <= W; j++) {
			if (j<w[i])dp[i + 1][j] = dp[i][j];
			else dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
		}
}

应用:最长公共子序列(Longest Common Subsequence)

定义:dp[i][j]:两个子序列分别到i和j的位置的LCS
状态转移如下:
dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1])(s[i+1]!=s[j+1])
dp[i+1][j+1]=dp[i][j]+1(其他)

void solve() {
	for(int i=0;i<n;i++)//此处不需要=
		for (int j = 0; j <m; j++) {
			if (s[i] == t[i])dp[i + 1][j + 1] = dp[i][j] + 1;
			else dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);
		}
}

2.进一步探索

完全背包问题

n的物品都有自己的wi,vi,背包最多可装载的重量为W,怎样使得背包里的物体的价值最大,每个物品可以选择任意多(1<<n<<100,1<=W<=10000,1<=wi,vi<=100
思考一下,首先我们可能会想到枚举k(k为每个物品选择的个数,现在就存在一个三重循环)进一步思考,由于每个物品可以选择任意多,当我们停留在选择某个物品上面,只有状态j会发生改变,所以转态转移如下
定义:dp[i+1][j]:从0到i(i+1个)物品中选出总重量不超过j的最大物品的价值和。(假设物体从第0个开始)
1. dp[0][j]=0 (dp[i][0]=0如果物品价值都大于0)
2.dp[i+1][j] (j<w[i])
dp[i+1][j]=max{dp[i][j],dp[i+1][j-w[i]]+v[i]}
写法于上面类似

void solve() {
	for(int i=0;i<n;i++)
		for (int j = 0; j <= W; j++) {
			if (v[i] > j)dp[i + 1][j] = dp[i][j];
			else dp[i + 1][j] = max(dp[i][j], dp[i + 1][j - w[i]] + v[i]);
		}
}

01背包和完全背包重复利用数组的情况

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]);
		}
}
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]);
		}
}

dp[i + 1][j] = max(dp[i][j], dp[i+1][j - w[i]] + v[i])这种情况计算[i+1][j]时候只用到[i]和[i+1],所以可以用到滚动数组,具体见白书。

01背包问题2

n的物品都有自己的wi,vi,背包最多可装载的重量为W,怎样使得背包里的物体的价值最大1<<n<<100,1<=W<=1e9,1<=wi<=1e7,1<=vi<=100
对于这个问题,相比之前的01背包,只是修改了限制条件的大小,之前的复杂度为O(nW),这个复杂度对于现在的规模是不够用了的,但是价值的范围较小,我们可以改变dp的对象。
定义:
dp[i+1][j]:前i个物品挑选总价值为J的最小总重量(不存在是为INF)。
dp[0][0]=0
dp[0][j]=INF
前i个物品挑选总价值为j状态可以由前i-1个物品挑选总价值为j的状态和前前i-1个挑选总价值为j-v[i]的状态转移而来。于是:
dp[i+1][j]=max(dp[i][j],dp[i][j-v[i]]+w[i])
最终的答案就是dp[n][j]<=W的最大j值。
时间复杂度为O(n*sigma(vi)),这种题就是要根据问题规模来设定算法的对象。

int solve() {
	//设立初状态
	//N表示n的最大值,V表示总价值的最大
	fill(dp[0], dp[0] + N * V + 1, INF);
	dp[0][0] = 0;
	for(int i=0;i<n;i++)
		for (int j = 0; j < N*V; j++) {
			if (j < v[i])dp[i + 1][j] = dp[i][j];
			else dp[i + 1][j] = min(dp[i][j], dp[i][j - v[i]] + w[i]);
		}
	int res = 0;
	for (int i = 0; i <= N * V; i++)if (dp[n][i] <= W)res = i;
	return res;
}

多重部分和问题

有n个大小不同的数字ai,每个数字有mi个,判断能否从中选择部分使得他们的和为K(1<=n<=100,1<=ai,mi<=1e5,1<=K<=1e5)
定义dp[i+1][j]:对于前i个数字加和得到j时,第i个数字的最多剩余(不能加和得到j的情况为-1)

dp[i+1][j]=m[i](dp[i][j]>=0,前i-1个数字已经加和得到j,此时第i个数字不需要加入)
          =-1(j<ai,or,dp[i+1][j-ai]<=0说明不管是否加入,都不能得到j)
          =dp[i+1][j-ai]-1(其他情况)

重复利用数组,还是有点不太清晰。

void solve() {
	memset(dp, -1, sizeof(dp));
	dp[0] = 0;
	for(int i=0;i<n;i++)
		for (int j = 0; j <= K; j++) {
			if (dp[j] >= 0)dp[j] = m[i];
			else if (j < a[i] || dp[j - a[i]] <= 0)dp[j] = -1;
			else dp[j] = dp[j - a[i]] - 1;
		}
}

最长上升子序列(Longest Increasing Subsequence)

在一个长为n的数列中(a0,a1,a2,a3...,an-1)。请你求出最长的上升子序列长度。(1<=n<=1000,0<=ai<=1000000)
定义dp[i]:以a[i]为结尾的最长上升子序列长度。
dp[i]=max{1, dp[j]+1|j<i&&aj<ai}

int n;
int a[MAX_N];
int dp[MAX_N];
void solve(){
	int res=0;
	for(int i=0;i<n;i++){
		dp[i]=1;
		for(int j=0;j<i;j++)
		if(a[j]<a[i]){
			dp[i]=max(dp[i],dp[j]+1)
		}
		res=max(res,dp[i]);
	}
	cout<<res<<endl;
}
不疯魔不成活
原文地址:https://www.cnblogs.com/gzr2018/p/10406452.html