zoukankan      html  css  js  c++  java
  • 记忆化结果再利用 进一步探讨递推关系

    参考《挑战算法设计竞赛》p.58

    https://www.cnblogs.com/Ymir-TaoMee/p/9419377.html

    完全背包问题:

    问题描述:有n种重量和价值分别为wi,vi的物品,从这些物品中挑选总重量不超过W的物品,求出挑选物品价值总和的最大值,在这里,每种物品可以挑选任意多件。

    input:

    3
    7
    3 4
    4 5
    2 3

    output:

    10 ( 0号物品选1个,2号物品选2个)

    这次同一种类的物品可以选择任意多件了,尝试着写出递推关系:
    dp[i+1][j] := 从前i+1种(编号0到i)物品中挑选总重量不超过j时总价值的最大值.
    dp[0][j]=0(前面0种)
    dp[i+1][j]=max{dp[i][j-k*w[i]]+k*v[i]|k≥0}

    c++版本解法:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int n,W;
    int * w;
    int * v;
    int **dp;
    
    int max(int x, int y)
    {
        if (x>y) return x;
        return y;
    }
    
    int main()
    {
        cin >> n >> W;
        w = new int[n];
        v = new int[n];
        for (int i=0; i<n; i++)
        {
            cin >> w[i] >>v[i];
        }
        dp = new int*[n+1];
        for (int i=0; i<=n; i++)
        {
            dp[i] = new int[W+1];
            memset(dp[i],0,sizeof(int)*(W+1));
        }
        for (int i=0; i<n; i++)
        {
            for (int j=0; j<=W; j++)
            {
                for (int k=0; k*w[i]<=j; k++)
                {
                    dp[i+1][j] = max(dp[i+1][j],dp[i][j-k*w[i]]+k*v[i]);
                }
            }
        }
        cout << dp[n][W] << endl;
    }
    

     Java版本解法:

    package 记忆化搜索;
    
    import java.util.Scanner;
    
    public class Main {
    
    	public static void main(String[] args) {
    		Scanner sc=new Scanner(System.in);
    		int n=sc.nextInt();
    		int W=sc.nextInt();
    		int []w=new int[n];
    		int []v=new int[n];
    		for (int i = 0; i < v.length; i++) {
    			w[i]=sc.nextInt();
    			v[i]=sc.nextInt();
    		}
    		int dp[][]=new int[n+1][W+1];
    		for (int i=0; i<n; i++)
    		{
    			for (int j=0; j<=W; j++)
    			{
    				for (int k=0; k*w[i]<=j; k++)
    				{
    					dp[i+1][j] = Math.max(dp[i+1][j],dp[i][j-k*w[i]]+k*v[i]);
    				}
    			}
    		}
    		System.out.println(dp[n][W]);
    	}
    
    }
    

    上面的程序是三重循环的,关于k的循环最坏可能从0到W,所以这个算法的复杂度为O(nW2),这样并不够好
    我们来找一找这个算法中多余的计算(已经知道结果的计算),在dp[i+1][j]的计算中选择k(k≥1)个的情况,与在dp[i+1][j-w[i]]的计算中选择k-1个情况是相同的,所以

    dp[i+1][j]的递推中k≥1部分的计算已经在dp[i+1][j-w[i]]的计算中完成了:
    dp[i+1][j]
    = max{dp[i][j-k*w[i]]+k*v[i]|k≥0}
    = max(dp[i][j],max{dp[i][j-k*w[i]]+k*v[i]|k≥1})
    = max(dp[i][j],max{dp[i][(j-w[i])-k*w[i]]+k*v[i]|k≥0}+v[i])
    = max(dp[i][j],dp[i+1][j-w[i]]+v[i])
    即:dp[i+1][j] = max(dp[i][j],dp[i+1][j-w[i]]+v[i])
    这样处理之后,就不需要关于k的循环了,现在的复杂度为O(nW)

    C++版本解法:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int n,W;
    int * w;
    int * v;
    int **dp;
    
    int max(int x, int y)
    {
        if (x>y) return x;
        return y;
    }
    
    int main()
    {
        cin >> n >> W;
        w = new int[n];
        v = new int[n];
        for (int i=0; i<n; i++)
        {
            cin >> w[i] >>v[i];
        }
        dp = new int*[n+1];
        for (int i=0; i<=n; i++)
        {
            dp[i] = new int[W+1];
            memset(dp[i],0,sizeof(int)*(W+1));
        }
        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+1][j-w[i]]+v[i]);
            }
        }
        cout << dp[n][W] << endl;
    }
    

      JAVA版本解法:

    package 记忆化搜索;
    
    import java.util.Scanner;
    
    public class Main{
    
    	public static void main(String[] args) {
    		Scanner sc=new Scanner(System.in);
    		int n=sc.nextInt();
    		int W=sc.nextInt();
    		int []w=new int[n];
    		int []v=new int[n];
    		for (int i = 0; i < v.length; i++) {
    			w[i]=sc.nextInt();
    			v[i]=sc.nextInt();
    		}
    		int dp[][]=new int[n+1][W+1];
    		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] = Math.max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
    	        }
    	    }
    		System.out.println(dp[n][W]);
    	}
    
    }
    

      利用滚动数组解决两种背包问题

    0-1背包问题

    C++版本解法:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int n,W;
    int * w;
    int * v;
    int *dp;
    
    int max(int x, int y)
    {
        if (x>y) return x;
        return y;
    }
    
    int main()
    {
        cin >> n >> W;
        w = new int[n];
        v = new int[n];
        for (int i=0; i<n; i++)
        {
            cin >> w[i] >>v[i];
        }
        dp = new int[W+1];
        memset(dp,0,sizeof(int)*(W+1));
        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]);
            }
        }
        cout << dp[W] << endl;
    }

    JAVA版本解法:

    package 记忆化搜索;
    
    import java.util.Scanner;
    
    public class Main {
    
    	public static void main(String[] args) {
    		Scanner sc=new Scanner(System.in);
    		int n=sc.nextInt();
    		int W=sc.nextInt();
    		int []w=new int[n];
    		int []v=new int[n];
    		for (int i = 0; i < v.length; i++) {
    			w[i]=sc.nextInt();
    			v[i]=sc.nextInt();
    		}
    		int dp[]=new int[W+1];
    		for (int i=0; i<n; i++)
    		{
    			for (int j=W; j>=w[i]; j--)
    			{
    				dp[j] = Math.max(dp[j],dp[j-w[i]]+v[i]);
    			}
    		}
    		System.out.println(dp[W]);
    	}
    
    }
    

     完全背包问题

    c++版本解法:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int n,W;
    int * w;
    int * v;
    int *dp;
    
    int max(int x, int y)
    {
        if (x>y) return x;
        return y;
    }
    
    int main()
    {
        cin >> n >> W;
        w = new int[n];
        v = new int[n];
        for (int i=0; i<n; i++)
        {
            cin >> w[i] >>v[i];
        }
        dp = new int[W+1];
        memset(dp,0,sizeof(int)*(W+1));
        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]);
            }
        }
        cout << dp[W] << endl;
    }
    

      Java版本解法:

    package 记忆化搜索;
    
    import java.util.Scanner;
    
    public class Main{
    
    	public static void main(String[] args) {
    		Scanner sc=new Scanner(System.in);
    		int n=sc.nextInt();
    		int W=sc.nextInt();
    		int []w=new int[n];
    		int []v=new int[n];
    		for (int i = 0; i < v.length; i++) {
    			w[i]=sc.nextInt();
    			v[i]=sc.nextInt();
    		}
    		int dp[]=new int[W+1];
    		 for (int i=0; i<n; i++)
    		    {
    		        for (int j=w[i]; j<=W; j++)
    		        {
    		            dp[j] = Math.max(dp[j],dp[j-w[i]]+v[i]);
    		        }
    		    }
    		System.out.println(dp[W]);
    	}
    
    }
    

    两者只有关于j的循环方向不同。

    除了上面的情况外,还有可能通过将两个数组滚动使用来实现重复利用,例如之前的
    dp[i+1][j] = max(dp[i][j], dp[i+1][j-w[i]]+v[i])
    这一递推式中,dp[i+1]计算时只需要dp[i]和dp[i+1],所以可以结合奇偶性写成:

    C++版本:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    int n,W;
    int * w;
    int * v;
    int *dp[2];
    
    int max(int x, int y)
    {
        if (x>y) return x;
        return y;
    }
    
    int main()
    {
        cin >> n >> W;
        w = new int[n];
        v = new int[n];
        for (int i=0; i<n; i++)
        {
            cin >> w[i] >>v[i];
        }
        dp[0] = new int[W+1];
        dp[1] = new int[W+1];
        memset(dp[0],0,sizeof(int)*(W+1));
        memset(dp[1],0,sizeof(int)*(W+1));
        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][j],dp[i & 1][j-w[i]]+v[i]); 
            }
        }
        cout << dp[n & 1][W] << endl;
    } 

    01背包问题之2

    • 问题描述:有n个重量和价值分别为wi、vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。

    input:

    4
    5
    2 3
    1 2
    3 4
    2 2

    output:

    7(选择0、1、3号物品)

    这一问题与最初的01背包问题相比,只是修改了限制条件的大小。此前求解此问题的方法的复杂度是O(nW),对于这一问题的规模来讲就不够用了。在这个问题中,相比较重量而言,价值的范围比较小,所以可以试着改变DP的对象。之前的方法中,我们用DP针对不同的重量限制计算最大的价值,最大值最多为n*最大物品的价值。在这里,我们不妨尝试着用DP针对不同的价值计算最小的重量:
    dp[i+1][j] := 从前i+1个物品(编号从0到i)中挑选出价值总和为j时总重量的最小值(不存在时就是一个充分大的数值INF)
    由于前0个物品中什么都挑选不了,所以初始值为
    dp[0][0] = 0
    dp[0][j] = INF
    此外,从前i个物品中挑选出价值总和为j时,一定有
    ①前i-1个物品中挑选价值总和为j的部分
    ②前i-1个物品中挑选价值总和为j-v[i]的部分,然后再选中第i个物品
    这两种方法之一,所以就得到递推式:
    dp[i+1][j] = min(dp[i][j], dp[i][j-v[i]]+w[i])
    最终的答案就对应于令dp[n][j]≤W的最大的j,这样求解的时间复杂度为O(n∑vi),对此题限制条件下的输入就可以在时间限制内解出了。当然如果价值变大了的话,这里的算法也变得不可行了,我们需要依据问题的规模来改变算法

    c++版本解法:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    const int INF = 0x3FFFFFF;
    int n,W;
    int * w;
    int * v;
    int **dp;
    
    int min(int x, int y)
    {
        if (x>y) return y;
        return x;
    }
    
    int main()
    {
        cin >> n >> W;
        w = new int[n];
        v = new int[n];
        int maxv=0;
        for (int i=0; i<n; i++)
        {
            cin >> w[i] >>v[i];
            if (v[i]>maxv) maxv=v[i];
        }
        dp= new int*[n+1];
        for (int i=0; i<=n; i++)
        {
            dp[i] = new int[n*maxv+1];
            for (int j=0; j<=n*maxv; j++) dp[i][j]=INF;
        }
        dp[0][0] = 0;
        for (int i=0; i<n; i++)
        {
            for (int j=0; j<=n*maxv; 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]);
            }
        }
        for (int i=n*maxv; i>=0; i--)
            if (dp[n][i] <= W)
            {
                cout << i << endl;
                break;
            }
    }

    java解法:

    package 记忆化搜索;
    
    import java.util.Scanner;
    
    public class Main {
    
    	public static void main(String[] args) {
    		int INF=1000;
    		Scanner sc=new Scanner(System.in);
    		int n=sc.nextInt();
    		int W=sc.nextInt();
    		int []w=new int[n];
    		int []v=new int[n];
    		int maxVal=0;
    		for (int i = 0; i < v.length; i++) {
    			w[i]=sc.nextInt();
    			v[i]=sc.nextInt();
    			if(maxVal<v[i])
    				maxVal=v[i];
    		}
    
    		int dp[][]=new int[n+1][n*maxVal+1];
    		for (int i=0; i<=n; i++)
    		{
    			for (int j=0; j<=n*maxVal; j++) dp[i][j]=INF;
    		}
    		dp[0][0] = 0;
    		for (int i=0; i<n; i++)
    		{
    			for (int j=0; j<=n*maxVal; j++)
    			{
    				if (j<v[i]) dp[i+1][j] = dp[i][j];
    				else dp[i+1][j] = Math.min(dp[i][j],dp[i][j-v[i]]+w[i]);
    			}
    		}
    		for (int i=n*maxVal; i>=0; i--)
    			if (dp[n][i] <= W)
    			{
    				System.out.println(i);
    				break;
    			}
    
    	}
    
    }
    

      

    加油啦!加油鸭,冲鸭!!!
  • 相关阅读:
    提问的智慧
    Linux下Tomcat的安装配置
    Advanced Puppet 系列的前言
    一个purge参数引发的惨案——从线上hbase数据被删事故说起
    从入门到精通Puppet的实践之路
    Juno Puppet Opertaors Meetup小结
    Openstack配置文件管理的变迁之路
    如何成为一名Top DevOps Engineer
    mod_wsgi的工作模式和配置
    解决PuppetDB Failed to submit 'replace facts'问题
  • 原文地址:https://www.cnblogs.com/clarencezzh/p/10370672.html
Copyright © 2011-2022 走看看