• 入门动态规划问题


    hihocoder这周欠了三题,于是今天一波结束了。然后发现这三个题目似乎都很简单,并且还是一类问题里面的。所有就写成一次的吧。

    动态规划问题,说起来,理论上是每个搞ACM的人都会学的,而且应该是最开始就学的。因为动态规划问题是各种各样比赛的宠儿啊,几乎每次比赛必出动态规划。楼教主的“男人八题”里面就有几个动态规划问题,是需要结合数据结构和动态规划才能解决的问题。不过不在这次范围内。


    当然,在写动态规划问题之前,显然是要推荐一波《背包九讲》的,毕竟写的很好。传送门


    1)数字三角形问题

    数字三角形问题其实本质上也就是选和不选问题。

    就以hihocoder-1037-数字三角形这个题目来说吧。

        2
       6 4
      1 2 8
     4 0 9 6
    6 5 5 3 6
    这个三角形,从最上面走到下面,每次只能向左下或者右下走,问最后的路径上的数的和最大为多少。

    如果单纯的采用贪心的策略走的话,2-6-2-9-5,于是最大路径变成了24,然而结果却是28,是2-4-8-9-5

    显然在第一步走错了。

    那我们试试用搜索的方法,搜索因为采用递归的方式,所以其实把每一种方案都选择了一下。

    2-6-1-4-6

    2-6-1-4-5

    ....

    2-4-8-9-5

    ....

    2-4-8-6-6

    显然用搜索时能够找到最后的结果的。

    但是每种方案都找出来了。总共有2^4种方案,所以对于任意的n,有2^(n-1)种方案,那么如果n是100,显然要找到2^99种方案,这样的效率,是非常可怕的。

    那我们再考虑下,发现,在搜索的时候,很多步骤是重复的。比如在2-6-2和2-4-2后面的几种方案,虽然都是一样的结果,但是却因为前面不同所以被重复计算了。这是导致效率低的原因。那该怎么解决呢?

    很简单,把后面运行的结果保存一下,每次遇到相同的直接用不就可以了么。

    所以假设f[i][j]表示从底部走到(i,j)这个位置的路径上所经过的最大路径和。这个状态我们发现是可以转移的。

    f[i+1][j]的状态只需要向上面走一个位置,就可以转移到f[i][j],同理f[i+1][j+1]也是这样

    然后就可以得到一个方程f[i][j] = max(f[i+1][j], f[i+1][j+1]) + val[i][j];

    得到这个方程就可以轻松的解决这个问题了。

    所以动态规划的核心思想,其实就是状态和状态转移方程。

    附上hihocoder-1037-数字三角形的ac代码:

    import java.util.Scanner;
    import java.io.BufferedInputStream;
    
    public class Main {
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Scanner in = new Scanner( new BufferedInputStream( System.in ) );
    		int n = in.nextInt();
    		int[][] a = new int[n][n];
    		int[][] dp = new int[n][n];
    		for( int i = 0; i < n; ++i )
    			for( int j = 0; j <= i; ++j )
    				a[i][j] = in.nextInt();
    		
    		for( int i = 0; i < n; ++i ) dp[n - 1][i] = a[n - 1][i];
    		for( int i = n - 2; i >= 0; --i )
    			for( int j = i; j >= 0; --j )
    				dp[i][j] = Math.max(  dp[i + 1][j], dp[i + 1][j + 1] ) + a[i][j];
    		System.out.println( dp[0][0] );
    	}
    }



    2)01背包

    这个背包问题就是选和不选的问题,从这个背包问题能衍生出很多问题,比如POJ-2184这个题目就是一个很好的01背包变形。

    不过今天是基础的背包,那我还是说基础的背包问题吧。

    就以hihocoder-1038-01背包这个题目来说吧。

    有n个奖品,m个奖卷。第i个奖品兑换需要w[i]个奖卷,这个奖品由v[i]的价值。问使用m个奖卷能换到的奖品价值最大为多少。

    其实也是一个选和不选的问题了,与上面那个三角形还是非常类似的。

    看到这个问题的时候就会有一种想法,就是强行搜索一波,把选和不选每个物品的两种情况都给搜索出来,这种不失为一种办法,但是确实很麻烦效率很低,即使物品只有30个也会一波GG,当然如果某些题目剪枝剪得非常棒那是另外一回事了。

    根据我们做上面那个三角形的经验,我们要找到一个状态,一个状态转移方程就好了。

    那这个状态怎么找呢?

    f[i][j]表示当装了第i个物品,并且花了j个奖卷之后所能获得的最大价值。

    这样就成功的找出来了。你要是问我这是怎么找到的。我也只能说一句无可奉告,毕竟我也只是学习了这些之后才知道的。

    所以状态转移方程就是f[i][j] = max(f[i - 1][j - w[i]] + v[i], f[i][j]);(可优化)

    所以直接给出hihocoder-1038-01背包的代码。

    import java.util.Scanner;
    import java.io.BufferedInputStream;
    
    public class Main {
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Scanner in = new Scanner( new BufferedInputStream( System.in ) );
    		int n = in.nextInt();
    		int m = in.nextInt();
    		int[] a = new int[n];
    		int[] b = new int[n];
    		for( int i = 0; i < n; ++i ){
    			a[i] = in.nextInt();
    			b[i] = in.nextInt();
    		}
    		int[] dp = new int[m + 10];
    		for( int i = 0; i < n; ++i ) {
    			for( int j = m; j >= a[i]; --j ) {
    				if( j >= a[i] ) 
    					dp[j] = Math.max(dp[j - a[i]] + b[i], dp[j]);
    			}
    		}
    		System.out.println( dp[m] );
    	}
    }

    3)完全背包

    其实在说完全背包之前,说下多重背包比较好。不过想想我这么懒,还是算了吧。

    hihocoder-1043-完全背包为例。

    首先能获取的是无限的,每种奖品能被无限次获取。看到这里内心一颤啊,居然无限次获取,那怎么搞啊。然而,虽然奖品是无限次获取的,但是手中的奖卷却是有限的。对于每种物品,能获得的物品数,也不过就是m / w[i]而已啊。

    所以因此,就成功的把完全背包转换成了多重背包。多重背包的解法很多,比如再转换成01背包去计算,或者利用二进制来优化多重背包。

    即,把多重背包最多能选的次数w,变成[2^0, 2^1, 2^2, 2^3,..., 2^k, w - 2^k]

    这样的效率比一次一次的找要高的多了。

    直接附hihocoder-1043-完全背包的ac代码:

    import java.util.Scanner;
    import java.io.BufferedInputStream;
    
    public class Main {
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Scanner in = new Scanner( new BufferedInputStream( System.in ) );
    		int n = in.nextInt();
    		int m = in.nextInt();
    		int[] a = new int[n];
    		int[] b = new int[n];
    		int[] c = new int[n];
    		for( int i = 0; i < n; ++i ){
    			a[i] = in.nextInt();
    			b[i] = in.nextInt();
    			c[i] = m / a[i];
    		}
    		int[] dp = new int[m + 10];
    		for( int i = 0; i < n; ++i ) {
    			int t = c[i], r = 1;
    			while( t > 0 ) {
    				if( r > t ) r = t;
    				t -= r;
    				
    				for( int j = m; j >= r * a[i]; --j ) {
    					dp[j] = Math.max( dp[ j - r * a[i] ] + r * b[i], dp[j]);
    				}
    				r <<= 1;
    			}
    		}
    		System.out.println( dp[m] );
    	}
    }


  • 相关阅读:
    解决office运行过程中的卡顿现象
    考试那些事诚信和内控
    JavaScript知识点总结
    JavaScript常见案例
    投影矩阵和视口变换矩阵
    重学前端(11)浏览器CSSOM:如何获取一个元素的准确位置
    重学前端(10)HTML链接:除了a标签,还有哪些标签叫链接?
    重学前端(8)CSS选择器:伪元素是怎么回事儿?
    重学前端(9) 浏览器DOM:你知道HTML的节点有哪几种吗?
    重学前端(11)CSS排版:正常流
  • 原文地址:https://www.cnblogs.com/wiklvrain/p/8179335.html
Copyright © 2020-2023  润新知