BuaaCoding100~103动态规划

零崎的朋友很多Ⅰ(完全背包)

题目

零崎有很多朋友,其中有一个叫做lfj的接盘侠。
lfj是一个手残,他和零崎一起玩网游的时候不好好打本,天天看拍卖行,没过多久,就成为了一个出色的商人。时间一长,虽然挣了不少钱,却没时间练级了。
作为lfj的友人,零崎实在看不下去,于是他决定帮lfj一把。当然了,零崎肯定不会自己动手,活还得你们来干。
lfj可以提供给你们拍卖行所有能买到物品的价格和利润,由于游戏产出不限,所以可以假定只要有钱,即使是同一种东西,多少个也都能买到手。lfj还会告诉你他初始的成本。虽然零崎想让你们给出一次交易中利润最大的购买方案,但是lfj觉得只要知道最大利润就可以了。

分析

完全背包问题,每个物品无限多。
和0-1背包唯一的区别在于内层循环的顺序。下面分别分析为何一个是逆序的,一个是顺序的。

为什么0-1背包内循环是逆序的

网上有不少人在解释,我就不仔处心积虑构造例子了,简单讲点high level的。
首先需要明确,使用一维数组的本质是压缩了一个矩阵,原本的状态转移方程是
dp[i][v] = max{dp[i-1][v], dp[i-1][v-c[i]] + v[i]}。这里i表示研究的范围扩大到第i个物品,v表示容量。所以在矩阵里看,更新当前位置,靠的是这个位置左上方的值。现在这些值被压缩到了一个维度上,那么每个位置的值其实是这一列上最大的一个,这个值是以后要用的,那么如果是顺序的,前面的值覆盖了,后面的更新会误把同一行的新值当成左上方的值,所以不对。

为什么完全背包内循环是顺序的

上面所说的矩阵视角依然适用。但是现在,顺着走看起来会覆盖,但是其实要的就是覆盖,所谓覆盖就是这一行的值更新了这一行后面的值,而同一行表示同意个物品,所以这就相当于是这个物品被选取了多次。
核心是回归到矩阵,看一下每个值更新需要之前取得的那些值,这些值究竟能否在合适的时刻压缩到那个行向量上。

解答

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
	int P,N;
	int p[400];
	int c[400];
	int dp[20005];
	while(scanf("%d%d", &P, &N) != EOF) {
		for (int i = 0; i < N; i++) {
			scanf("%d%d", &p[i], &c[i]);
		}
		memset(dp, 0, sizeof(dp));
		for (int i = 0; i < N; i++) {
			for (int j = c[i]; j <= P; j++) {
				if (dp[j] < dp[j - c[i]] + p[i]) {
					dp[j] = dp[j - c[i]] + p[i];
				}
			}
		}
		printf("%d\n", dp[P]);
	}
	return 0;
}

零崎的朋友很多Ⅱ(满0-1背包)

题目

零崎有很多朋友,其中有一个叫做lfj的接盘侠。
lfj是一个手残,他和零崎一起玩网游的时候不好好打本,天天看拍卖行,没过多久,就成为了一个出色的商人。不过再出色的投机商也有失手成为接盘侠的一天。所谓真正的接盘侠从来不给自己留活路。当lfj接盘成功之时,即分文不剩之日。
作为lfj的友人,零崎实在看不下去,于是他决定帮lfj一把。当然了,零崎肯定不会自己动手,活还得你们来干。
lfj可以提供给你们拍卖行所有能买到物品的价格和利润,还有他的本金。既然是接盘侠,就必须分文不剩。虽然零崎想让你们给出一次接盘中利润最大的购买方案,但是lfj觉得只要知道最大利润就可以了。
每组数据第一行为两个整数P和N,表示本金和拍卖行物品个数。(注意:每类物品只有一件
接下来N行,每行两个数据pi,ci代表第i类物品的利润和购买价格。
1<=P<=20000,1<=N<=300,1<=c,p<=200

分析

初始化的艺术。不同的背包初始化之后都是0,表示一个不装。但是这个问题,初始化只有容量为0的位置,其他位置用特殊值表示非法。这样的话从子问题开始,只有恰好装满的时候才有效,这样产生的大问题也只有装满才有效。

解答

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
	int P,N;
	int p[400];
	int c[400];
	int dp[20005];
	while(scanf("%d%d", &P, &N) != EOF) {
		for (int i = 0; i < N; i++) {
			scanf("%d%d", &p[i], &c[i]);
		}
		memset(dp, -1, sizeof(dp));
		dp[0] = 0;
		for (int i = 0; i < N; i++) {
			for (int j = P; j >= c[i]; j--) {
				if (dp[j-c[i]]!=-1 && dp[j] < dp[j-c[i]] + p[i]) {
					dp[j] = dp[j-c[i]] + p[i];
				}
			}
		}
		if (dp[P] == -1) {
			printf("jpx\n");
		} else {
			printf("%d\n", dp[P]);
		}
	}
	return 0;
}

零崎的朋友很多Ⅲ (区间dp)

题目

现在jhljx遇到了矩阵乘法,他当时就懵了。数都数不清的他,矩阵乘法怎么可能会算的清楚呢?虽然零崎觉得还不如让你们来算,不过好歹也要给jhljx个面子,给她留下一个证明自己数学实力的机会。为了减小jhljx的计算量,让他赶快算出不正确答案来(估计他算上几遍都不一定能出一个正确答案),零崎请你们帮助jhljx。
输入
多组输入数据。
每组数据以N开始,表示矩阵链的长度。接下来一行N+1个数表示矩阵的行/列数。
1<=N<=300
输出
对于每组样例,输出一行最少运算次数的方案,每两个矩阵相乘都用“()”括起来,详见样例
如果存在多种解决方案,最终输出结果选取先计算左边的矩阵,详见Hint

分析

经典的区间dp,注意更新的顺序问题。
此外这道题的输出很有意思,需要递归实现。当时居然想出来了,感动到流泪。。。

解答

#include <stdio.h>
#include <string.h>
void output(int s[][305], int l, int r) {
	if (l == r) {
		printf("A%d", l + 1);
		return;
	}
	printf("(");
	int cut = s[l][r];
	output(s, l, cut-1);
	output(s, cut, r);
	printf(")");
	
}
int dp[305][305];
int s[305][305];
int main(int argc, char *argv[]) {
	int N;
	int val[400];
	while(scanf("%d", &N) != EOF) {
		for (int i = 0; i <= N; i++) {
			scanf("%d", &val[i]);
		}
		memset(dp, -1, sizeof(dp));
		memset(s, -1, sizeof(s));
		for (int i = 0; i < N; i++) {
			dp[i][i] = 0;
		}
		for (int i = N - 1; i >= 0; i--) {
			for (int j = i + 1; j < N; j++) {
				for (int k = i + 1; k <= j; k++) {
					if (dp[i][j] == -1 || dp[i][j] >= dp[i][k-1] + dp[k][j] + val[i]*val[k]*val[j+1]) {
						dp[i][j] = dp[i][k-1] + dp[k][j] + val[i]*val[k]*val[j+1];
						s[i][j] = k;
					}
				}
			}
		}
		output(s, 0, N-1);
		//printf("%d\n", dp[0][N-1]);
		printf("\n");
	}
}

双“11”的抉择

题目

把钱花完了,所以单身了,单身了所以过双“11”,过双“11”所以把钱花完了。
今年Nova君(三号)照旧过着他暗无天日的“买买买”的双“11”,然而因为囊中羞涩,并不能够太任性。他的购物车中,列满了数不清的商品,共有N件,好多商品居然还不止一件 _(:3 」∠) 现在Nova君要做出一个艰难的抉择,他要从所有商品中挑出m件拼成一个订单,请问有多少种凑单的方法呢?求访法数对M的余数。
PS:同一种商品不作区分。
输入
多组测试数据(不超过100组)
每组数据两行,第一行为三个正整数N,m,M,具体意义详见描述,第二行为N个正整数a1,a2,an,代表第i个商品的个数
(1<=N,ai,m<=1000,2<=M<=10000)
输出
对于每组数据,输出一行,表示方法总数

分析

属于多重集合组合数问题,很像高考数学题,以为是个数学题,结果因为这道题里选取的物品个数、种类都在变,所以写不出组合数公式。
这个动态规划好难啊。。。。
状态分析
设dp[i][j] 为考虑前i个物品时,取j个的方案数。
当研究包含i个物品取j个的时候,方案可以分为两类。
第一类:不包含第i个物品,这类方案数为dp[i-1][j]
第二类:至少包含一个第i个物品,这类方案数如下(注意同种物品算一个,所以同一种物品挑多个的时候只会有一种方案。)
dp[i-1][j-1] + dp[i-1][j-2] + …+dp[i-1][0],加上第一类,总方法数为
dp[i][j] = dp[i-1][j] + dp[i-1][j-1] + dp[i-1][j-2] + …+dp[i-1][0]
看上去是三重循环?这样显然会超时啊。
但是如果观察形式特征,
dp[i][j-1] = dp[i-1][j-1] + dp[i-1][j-2] +…+dp[i-1][0] (以j<=a[i-1]为例)
所以dp[i][j]可以写成dp[i][j-1] + dp[i-1][j]
基于这样的思路,可以化简最后一层的时间复杂度
如果j<=a[i-1],如上所述dp[i][j] = ap[i][j-1] + dp[i-1][j]
反之j>a[i-1],dp[i][j-1] = dp[i-1][j-1] + dp[i-1][j-2] +…+dp[i-1][j-1-a[i-1]]
dp[i][j] =dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1-a[i-1]] (减掉的这一项,是因为不能一直到0,所以把最后的项都去掉,注意我们前面的讨论里一直是拓展到0的)

解答

#include <stdio.h>
#define N 1010
int main(int argc, char * argv[]) {
	int a[N];
	int dp[N][N];
	int n,m,M;
	while(scanf("%d%d%d",&n,&m,&M) != EOF) {
		for(int i = 0; i < n; i++) {
			scanf("%d",&a[i]);
		}
		for(int i = 0; i <= n; i++) {
			dp[i][0] = 1;
		}
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= m; j++) {
				if(j -1 - a[i-1] >= 0) {
					dp[i][j] = (dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1-a[i-1]] +M)%M;
				} else {
					dp[i][j] = (dp[i-1][j] + dp[i][j-1])%M;
				}
            }
		}
        printf("%d\n",dp[n][m]);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值