• 记忆化搜索--(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;
    }
    
    不疯魔不成活
  • 相关阅读:
    表格标签
    常用标签
    标签笔记
    基础标签与格式
    态度!
    如何修改数据 练习
    增删查练习
    登陆注册练习
    PHP 数据访问
    PHP 基础知识测试题 答案分析
  • 原文地址:https://www.cnblogs.com/gzr2018/p/10406452.html
Copyright © 2020-2023  润新知