动态规划及实例

01揹包问题:一个容量为C的揹包,n个物品,他们的重量分别是w[i],价值分别是v[i],怎样选择装入的物品使价值最大?

分析:面对每个物品,我们只有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。
所以声明一个 大小为  m[n][c] 的二维数组,m[ i ][ j ] 表示 在面对第 i 件物品,且揹包容量为  j 时所能获得的最大价值 ,那么我们可以很容易分析得出 m[i][j] 的计算方法。
(1). j < w[i] 的情况,这时候揹包容量不足以放下第 i 件物品,只能选择不拿
m[ i ][ j ] = m[ i-1 ][ j ]
(2). j>=w[i] 的情况,这时揹包容量可以放下第 i 件物品,我们则考虑是否拿这件物品可以使价值最大化。
如果拿,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,揹包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。

如果不拿,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)。

那么怎么知道价值最大呢?比较即可。即max(m[i-1][j],m[i-1][j-w[i]]+v[i])。

这里不给出代码了,主要是利用它的思想去解决问题。请看下面的实例:

一、最小邮票数

问题描述: 有若干张邮票,要求从中选取最少的邮票张数凑成一个给定的总值。     如,有1分,3分,3分,3分,4分五张邮票,要求凑成10分,则使用3张邮票:3分、3分、4分即可。

分析:其实和揹包问题类似。我选择了第一张邮票的面值,后面还可以继续选么?可以的,所以从1-n做完,那么如果我第一次选择了第二张的面值,在去选择第一张的是不是和第一次选择1的面额再选择2一样,所以2-n往后继续做,以此类推循环就出来了。我选择了邮票后,把这个面值减去,就是我剩下的吧,但是我的余下的面值足够大,才可以选择剩下的面额的邮票吧?这其实和把东西装进揹包里一样吧。只是这里求最小邮票的个数,所以是min().具体的一些改变和说明我在代码中已经加了注释,这里就不过多解释了。

#include<iostream>
#define INF 0x11111111
#include<string.h>
using namespace std;
int dp[1010];
int min(int a, int b){
	return a < b ? a : b;
}
int main(){
	int m, n;
	while(cin >> m){
		cin >> n;
		int a[n];
		for(int i = 0; i < n; i ++){
			cin >> a[i];
		}
		memset(dp, INF, sizeof(dp));//sizeof()返回一个变量或者类型的大小, 单位是字节 
//void *memset(void *s,int c,size_t n) 总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。
		dp[0] = 0;
 		for(int i = 0; i < n; i ++){//可以选择从第一个开始往后,一组选完之后;从第二个数往后,进行新的一组选择.....
			for(int j = m; j >= a[i]; j --){//j >= a[i]要能装的下
				dp[j] = min(dp[j], dp[j - a[i]] + 1);
                //dp[i - a[i]]前j - 1个的质量,因为这里是计算个数 所以把这个装进去就需要加1.
			}
		}
		if(dp[m] >= 1000){
			printf("0\n");
		} 
		else{
			printf("%d\n", dp[m]);
		}
    }
	return 0;
}

二、最大序列和

问题描述:给出一个整数序列S,其中有N个数,定义其中一个非空连续子序列T中所有数的和为T的“序列和”。 对于S的所有非空连续子序列T,求最大的序列和。 变量条件:N为正整数,N≤1000000,结果序列和在范围(-2^63,2^63-1)以内。

分析:我们选择数往序列里面加,并且求这个序列和。利用动态方程dp[j] = max{dp[j -1] + a[i] ,a[i]},什么意思呢?

当dp[j-1]+a[i]>a[i]时,即dp[j-1]>0。就是a[i]前一段的序列和目前看来是大于0的,根据最优原则,当然段长要继续加a[i],使序列和最大。

反之,当dp[j-1]<0时,肯定不能加上含有a[i]的序列,(因为要是dp[j-1]加上之后,序列和减小了),此时a[i]作为新序列的首元素,dp[j]=a[i]。

注意就是这里的数的值很大,大于int ,所以用long long定义。而且储存的数组等也要保持足够的空间。否则可能导致溢出,从而报段出错。

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#define MAX 10000001
using namespace std;
int main(){//动态规划 
    long long *a, *dp, n;
    a = (long long *) malloc( MAX * sizeof(long long)); 
    dp = (long long *)malloc( MAX * sizeof(long long)); 
	while(cin >> n){
//		long long a[n];     
		for(long long i = 0; i < n; i ++){
			cin >> a[i];
		}
		dp[0] = a[0]; 
		long long  maxnum = a[0];
		for(long long j = 1; j < n; j ++){
			dp[j] = (dp[j - 1] + a[j]) > a[j] ? (dp[j - 1] + a[j]) : a[j];
		if(maxnum < dp[j]){
			maxnum = dp[j];
			}
		}
			cout << maxnum << endl;
    }
	return 0;
}

以上就是该篇的主要内容。欢迎指出错误和提出改进意见,谢谢!

点赞