2021 GDUT 新生专题训练 动态规划

4 篇文章 0 订阅
4 篇文章 0 订阅

动态规划

知识总结

DP基本思路

  • 确定范围,限制,操作
  • 确定边界条件与初始条件
  • 确定转移方程

题解

题目

A - 送快弟

现在我们有N个配件,他们有不同的价值. 但是我们背包的容量是有限的,因为我们只有一个一级包, 所以我们最多可以装V重量的东西. 但是为了能更好的吃到鸡(不存在的)我们要携带更有价值的配件,请问我们最多能拿多少价值的配件来当快递员呢??

input

输入的第一行是T, 表示有一共要打T场比赛.

每组数据由三行组成.

第一行包含两个整数N和V(N <= 1000, V <= 1000). N表示配件的个数, V表示一级包的大小(系统会更新嘛).

第二行包含N个整数, 表示每一个配件的价值.

第三行包含N个整数, 表示每个配件的重量.

output

对每一组数据, 输出我们最多能拿多少价值的配件.

样例
input
1
10 10
1 3 5 7 9 11 13 15 17 19
19 17 15 13 11 9 7 5 3 1
output
51
思路
范围:N个配件
限制:V重量
操作:取或不取

综上可得,设f[i][j][0/1]表示从1->n,重量为j时,取或不取i物品的最大价值
其中f[0][0][0] = f[0][0][1] = 0;
转移方程可写为:
f[i][j][0] = f[i-1][j][0]
f[i][j][1] = f[i-1][j-w[i]][1]+v[i];
已知第三维度不取即继承上一态,可知可将第三维度去掉变成
f[i][j] = max (f[i-1][j] , f[i-1][j-w[i]]+v[i])
由于数据只用到了i-1的,由此可知可用滚动数组优化
假如此时去掉第一维,方程变成
f[j] = max (f[j] , f[j-w[i]]+v[i])
由于j-w[i] < j,若正序遍历则会覆盖数据,导致一个物品可能可以选取多次
故倒序即可,可知为一维dp
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;
int dp[N] , V[N] , W[N];
int max (int a , int b) { return a > b ? a : b; }
int main () {
	int t , n , v;
	cin >> t;
	while (t--) {
		cin >> n >> v;
		for (int i = 1 ; i <= n ; ++i) cin >> V[i];
		for (int i = 1 ; i <= n ; ++i) cin >> W[i];
		
		memset (dp , 0 , sizeof (dp));
		
		for (int i = 1 ; i <= n ; ++i) 
			for (int j = v ; j >= W[i] ; --j) dp[j] = max (dp[j] , dp[j-W[i]] + V[i]);
		
		cout << dp[v] << endl;
	}
	return 0;
}

题目

B - CD

You have a long drive by car ahead. You have a tape recorder, but unfortunately your best music is on CDs. You need to have it on tapes so the problem to solve is: you have a tape N minutes long. How to choose tracks from CD to get most out of tape space and have as short unused space as possible.

Assumptions:

  • number of tracks on the CD. does not exceed 20
  • no track is longer than N minutes
  • tracks do not repeat
  • length of each track is expressed as an integer number
  • N is also integer

Program should find the set of tracks which fills the tape best and print it in the same sequence as the tracks are stored on the CD

input

Any number of lines. Each one contains value N, (after space) number of tracks M and durations of the tracks. For example from first line in sample data: N=5, M=3, first track lasts for 1 minute, second one 3 minutes, next one 4 minutes

The input data satisfies the following constraints:

N≤10000
M≤20

output

Set of tracks (and durations) which are the correct solutions and string ```sum:`" and sum of duration times.

样例
input
5 3 1 3 4
10 4 9 8 4 2
20 4 10 5 7 4
90 8 10 23 1 2 3 4 5 7
45 8 4 10 44 43 12 9 8 2
output
1 4 sum:5
8 2 sum:10
10 5 4 sum:19
10 23 1 2 3 4 5 7 sum:55
4 10 12 9 8 2 sum:45
思路
范围:M个轨道
限制:N个空间
操作:取或不取

分析同上题,易得同为01背包问题,将每个轨道的长度看作重量与价值,有路径需要输出,则记录前驱即可
设f[i]为空间为i时的最大利用数
f[i] = max (f[i] , f[i-w[i]] + w[i])
代码
#include <cstdio>
using namespace std;
const int N = 10010;
int dp[N] , p[N] , w[N];
int max (int a , int b) { return a > b ? a : b; }
void print (int sw) {
	if (!p[sw]) return ;
	else print (sw - p[sw]);;
	printf ("%d " , p[sw]);
} 
int main () {
	int n , m;
	while (scanf ("%d %d" , &n , &m) != EOF) {
		for (int i = 1 ; i <= m ; ++i) scanf ("%d" , &w[i]);
		
		for (int i = 1 ; i <= n ; ++i) dp[i] = 0 , p[i] = 0;
		
		for (int i = 1 ; i <= m ; ++i) 
			for (int j = n ; j >= w[i] ; --j) 
				if (dp[j-w[i]] + w[i] > dp[j]) {
					dp[j] = dp[j-w[i]] + w[i];
					p[j] = w[i];
				}
		
		print (dp[n]);
		printf ("sum:%d\n" , dp[n]);
		
	}
	return 0;
}

题目

C - Unidirectional TSP

Problems that require minimum paths through some domain appear in many different areas of computer science. For example, one of the constraints in VLSI routing problems is minimizing wire length. The Traveling Salesperson Problem (TSP) — finding whether all the cities in a salesperson’s route can be visited exactly once with a specified limit on travel time — is one of the canonical examples of an NP-complete problem; solutions appear to require an inordinate amount of time to generate, but are simple to check.

​ This problem deals with finding a minimal path through a grid of points while traveling only from left to right.

​ Given an m×n matrix of integers, you are to write a program that computes a path of minimal weight. A path starts anywhere in column 1 (the first column) and consists of a sequence of steps terminating in column n (the last column). A step consists of traveling from column i to column i + 1 in an adjacent (horizontal or diagonal) row. The first and last rows (rows 1 and m) of a matrix are considered adjacent, i.e., the matrix “wraps” so that it represents a horizontal cylinder. Legal steps are illustrated on the right.

​ The weight of a path is the sum of the integers in each of the n cells of the matrix that are visited.

​ For example, two slightly different 5×6 matrices are shown below (the only difference is the numbers in the bottom row). The minimal path is illustrated for each matrix. Note that the path for the matrix on the right takes advantage of the adjacency property of the first and last rows.

input

The input consists of a sequence of matrix specifications. Each matrix specification consists of the row and column dimensions in that order on a line followed by m · n integers where m is the row dimension and n is the column dimension. The integers appear in the input in row major order, i.e., the first n integers constitute the first row of the matrix, the second n integers constitute the second row and so on. The integers on a line will be separated from other integers by one or more spaces. Note: integers are not restricted to being positive.

​ There will be one or more matrix specifications in an input file. Input is terminated by end-of-file.

​ For each specification the number of rows will be between 1 and 10 inclusive; the number of columns will be between 1 and 100 inclusive. No path’s weight will exceed integer values representable using 30 bits.

output

Two lines should be output for each matrix specification in the input file, the first line represents a minimal-weight path, and the second line is the cost of a minimal path. The path consists of a sequence of n integers (separated by one or more spaces) representing the rows that constitute the minimal path. If there is more than one path of minimal weight the path that is lexicographically smallest should be output.

Note: Lexicographically means the natural order on sequences induced by the order on their elements.

样例
input
5 6
3 4 1 2 8 6
6 1 8 2 7 4
5 9 3 9 9 5
8 4 1 3 2 6
3 7 2 8 6 4
5 6
3 4 1 2 8 6
6 1 8 2 7 4
5 9 3 9 9 5
8 4 1 3 2 6
3 7 2 1 2 3
2 2
9 10 9 10
output
1 2 3 4 4 5
16
1 2 1 5 4 5
11
1 1
19
思路
范围:n,n
限制:无
操作:右上,右,右下

与数字三角形类似,但边界条件需要特殊计算,由于需要按照字典序输出答案,故需要倒序输出结果!important
设f[i][j]为在(i,j)这个点时的最短路径
x = calc (i+opt) opt:{-1,0,1} 用于判断真正的落点
f[i][j] = min (f[i][j] , f[x][j+1]+cost(x,j+1))
代码
#include <cstdio>
using namespace std;
int dp[15][105] , ma[15][105] , p[15][105] , n , m;
int calc (int x) {
	if (x == 0) return n;
	else if (x > n) return 1;
	else return x;
}
int min (int a , int b) {
	return a > b ? b : a;
}
void print (int j , int k) {
	if (j > 0) print (j-1 , p[k][j]);
	else return ;
	printf ("%d " , k);
}
int main () {
	while (scanf ("%d %d" , &n , &m) != EOF) {
		for (int i = 1 ; i <= n ; ++i)
			for (int j = 1 ; j <= m ; ++j) {
				scanf ("%d" , &ma[i][j]);
				p[i][j] = 2147483647; dp[i][j] = 2147483647;
			}
		
		for (int i = 1 ; i <= n ; ++i) dp[i][m] = ma[i][m];
		
		for (int j = m - 1 ; j >= 1 ; --j) {
			for (int i = 1 ; i <= n ; ++i) {
				for (int k = -1 ; k <= 1 ; ++k) {
					int y = calc(i+k);
					if (dp[i][j] > dp[y][j+1] + ma[i][j]) {
						dp[i][j] = dp[y][j+1] + ma[i][j];
						p[i][j] = y;
					} else if (dp[i][j] == dp[y][j+1] + ma[i][j]) {
						p[i][j] = min (p[i][j] , y);
					}
				}
			}
		}
		int minp = 1;
		for (int i = 1 ; i <= n ; ++i)
			if (dp[minp][1] > dp[i][1]) minp = i;
		int next = minp;
		for (int i = 1 ; i <= m ; ++i) {
			printf ("%d" , next);
			if (i != m) printf (" ");
			next = p[next][i];
		}
		printf ("\n%d\n" , dp[minp][1]);
	}
	return 0;
}

题目

D - 猪钱罐

在 ACM 能够开展之前,必须准备预算,并获得必要的财力支持。该活动的主要收入来自于 Irreversibly Bound Money (IBM)。思路很简单。任何时候,某位 ACM 会员有少量的钱时,他将所有的硬币投入到小猪储钱罐中。这个过程不可逆,因为只有把小猪储钱罐打碎才能取出硬币。在足够长的时间之后,小猪储钱罐中有了足够的现金,用于支付 ACM 活动所需的花费。

但是,小猪储钱罐存在一个大的问题,即无法确定其中有多少钱。因此,我们可能在打碎小猪储钱罐之后,发现里面的钱不够。显然,我们希望避免这种不愉快的情况。唯一的可能是,称一下小猪储钱罐的重量,并尝试猜测里面的有多少硬币。假定我们能够精确判断小猪储钱罐的重量,并且我们也知道给定币种的所有硬币的重量。那么,我们可以保证小猪储钱罐中最少有多少钱。

你的任务是找出最差的情形,即判断小猪储钱罐中的硬币最少有多少钱。我们需要你的帮助。不能再贸然打碎小猪储钱罐了!

input

输入包含 T 组测试数据。输入文件的第一行,给出了 T 的值。

对于每组测试数据,第一行包含 E 和 F 两个整数,它们表示空的小猪储钱罐的重量,以及装有硬币的小猪储钱罐的重量。两个重量的计量单位都是 g (克)。小猪储钱罐的重量不会超过 10 kg (千克),即 1 <= E <= F <= 10000 。每组测试数据的第二行,有一个整数 N (1 <= N <= 500),提供了给定币种的不同硬币有多少种。接下来的 N 行,每行指定一种硬币类型,每行包含两个整数 P 和 W (1 <= P <= 50000,1 <= W <=10000)。P 是硬币的金额 (货币计量单位);W 是它的重量,以 g (克) 为计量单位。

output

对于每组测试数据,打印一行输出。每行必须包含句子 “The minimum amount of money in the piggy-bank is X.” 其中,X 表示对于给定总重量的硬币,所能得到的最少金额。如果无法恰好得到给定的重量,则打印一行 “This is impossible.” 。

样例
input
3
10 110
2
1 1
30 50
10 110
2
1 1
50 30
1 6
2
10 3
20 4
output
The minimum amount of money in the piggy-bank is 60.
The minimum amount of money in the piggy-bank is 100.
This is impossible.
思路
范围:n种硬币
限制:重量为V
操作:选几枚

设f[i][j][k]为当前硬币为i 重量为j 选了k枚i硬币的最小价值
f[i][j][0] = min(f[i-1][j][0..k])
f[i][j][k] = f[i][j-w[i]][k-1]+v[i]
尝试降维,第一个转移方程可知f[i-1][j][0...k]只有最小值是意义的
故去掉第三维,方程变为
f[i][j] = min(f[i-1][j-k*w[i]]+k*v[i]) k=0....n
再一看,第一维也可以去掉,方程变为
j-k*w[i] < j 若k=2时,只需从k=1的方案进行转移,故可知正序遍历时可达到效果
方程:f[j] = min (f[j] , f[j-w[i]]+v[i]) 为完全背包
代码
#include <cstdio>
using namespace std;
int v[505] , w[505] , dp[10005];
int main () {
	int t , e , f , n;
	scanf ("%d" , &t);
	while (t--) {
		scanf ("%d %d %d" , &e , &f , &n);
		for (int i = 1 ; i <= n ; ++i) scanf ("%d %d" , &v[i] , &w[i]);
		for (int i = 1 ; i <= f - e ; ++i) dp[i] = 1e9+7;
		for (int i = 1 ; i <= n ; ++i)
			for (int j = w[i] ; j <= f-e ; ++j) 
				if (dp[j] > dp[j-w[i]] + v[i]) dp[j] = dp[j-w[i]] + v[i];
			
		if (dp[f-e]!=1e9+7) printf ("The minimum amount of money in the piggy-bank is %d.\n" , dp[f-e]);
		else printf ("This is impossible.\n");
	}
}

题目

E - 划分

现有面值为1、2、3、4、5、6的硬币若干枚,现在需要知道能不能将这些硬币分成等额的两堆。

input

每行输入6个正整数,分别表是面值为1、2、3、4、5、6的硬币的个数,若输入6个0代表输入结束。单种硬币的数量不会超过20000。

output

若能分割,输出 Can be divided.'',若不能输出Can’t be divided.’’

样例
input
1 0 1 2 0 0
1 0 0 0 1 1
0 0 0 0 0 0
output
Collection #1:
Can't be divided.

Collection #2:
Can be divided.
思路
范围:6种硬币n枚
限制:总价值的一半
操作:取几枚

此时分析可得将已有的硬币总额加起来,若不能被2整除则不能分成,若可以则进行dp
设f[i][j][k]为第i枚硬币,总价值为j,取k枚
则方程为
f[i][j][0] = max(f[i-1][j][0....k])
f[i][j][k] = f[i][j-w[i]][k-1]+w[i]
去第三维可得
f[i][j] = max (f[i-1][j-k*w[i]]+k*w[i]) k=0...n(n枚i种硬币)
此时k有限制条件非完全背包类型,由于空间优化的需要,故可以根据二进制原理将k种i枚硬币分为i,2i,4i....以此降低复杂度
此时问题转化为选取或不选取,为01背包问题 故第一维可去
f[j] = max(f[j] , f[j-w[i]]+w[i])
最后判断f[sum/2] == sum/2即可
代码
质朴超时代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[10] , list[200000] , dp[200000];
int main () {
	int t = 1;
	while (true) {
		int sum = 0 , index = 0;
		for (int i = 1 ; i <= 6 ; ++i) {
			scanf ("%d" , &a[i]);
			sum += a[i] * i;
		}
		if (sum == 0) break;
		if (sum % 2) {
			printf ("Collection #%d:\nCan't be divided.\n\n" , t++);
			continue;
		}
		sum >>= 1;
		
		memset (dp , 0 , sizeof (dp));
		
		for (int i = 1 ; i <= 6 ; ++i) 
			for (int k = 0 ; k <= a[i] ; ++ k) {
				for (int j = sum ; j >= k*i ; -- j) {
					dp[j] = max (dp[j] , dp[j-k*i]+k*i);
				}
			}
		if (dp[sum] == sum) printf ("Collection #%d:\nCan be divided.\n\n" , t);
		else printf ("Collection #%d:\nCan't be divided.\n\n" , t);
		t++;
	}
	return 0;
}
正解
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[10] , list[200000] , dp[200000];
int main () {
	int t = 1;
	while (true) {
		int sum = 0 , index = 0;
		for (int i = 1 ; i <= 6 ; ++i) {
			scanf ("%d" , &a[i]);
			sum += a[i] * i;
		}
		if (sum == 0) break;
		if (sum % 2) {
			printf ("Collection #%d:\nCan't be divided.\n\n" , t++);
			continue;
		}
		sum >>= 1;
		for (int i = 1 ; i <= 6 ; ++i) {
			int c = 1;
			while (a[i] - c > 0) {
				a[i] -= c;
				list[++index] = c * i;
				c <<= 1;
			}
			list[++index] = a[i] * i;
		}
		
		memset (dp , 0 , sizeof (dp));
		
		for (int i = 1 ; i <= index ; ++i) 
			for (int j = sum ; j >= list[i] ; --j) dp[j] = max (dp[j] , dp[j-list[i]]+list[i]);
		
		if (dp[sum] == sum) printf ("Collection #%d:\nCan be divided.\n\n" , t);
		else printf ("Collection #%d:\nCan't be divided.\n\n" , t);
		t++;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值