算法自学__背包动态规划

例1 P5020 [NOIP2018 提高组] 货币系统

题目描述

在网友的国度中共有 n n n 种不同面额的货币,第 i i i 种货币的面额为 a [ i ] a[i] a[i],你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为 n n n、面额数组为 a [ 1.. n ] a[1..n] a[1..n] 的货币系统记作 ( n , a ) (n,a) (n,a)

在一个完善的货币系统中,每一个非负整数的金额 x x x 都应该可以被表示出,即对每一个非负整数 x x x,都存在 n n n 个非负整数 t [ i ] t[i] t[i] 满足 a [ i ] × t [ i ] a[i] \times t[i] a[i]×t[i] 的和为 x x x。然而, 在网友的国度中,货币系统可能是不完善的,即可能存在金额 x x x 不能被该货币系统表示出。例如在货币系统 n = 3 n=3 n=3, a = [ 2 , 5 , 9 ] a=[2,5,9] a=[2,5,9] 中,金额 1 , 3 1,3 1,3 就无法被表示出来。

两个货币系统 ( n , a ) (n,a) (n,a) ( m , b ) (m,b) (m,b) 是等价的,当且仅当对于任意非负整数 x x x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。他们希望找到一个货币系统 ( m , b ) (m,b) (m,b),满足 ( m , b ) (m,b) (m,b) 与原来的货币系统 ( n , a ) (n,a) (n,a) 等价,且 m m m 尽可能的小。他们希望你来协助完成这个艰巨的任务:找到最小的 m m m

输入格式

输入文件的第一行包含一个整数 T T T,表示数据的组数。

接下来按照如下格式分别给出 T T T 组数据。 每组数据的第一行包含一个正整数 n n n。接下来一行包含 n n n 个由空格隔开的正整数 a [ i ] a[i] a[i]

输出格式

输出文件共有 T T T 行,对于每组数据,输出一行一个正整数,表示所有与 ( n , a ) (n,a) (n,a) 等价的货币系统 ( m , b ) (m,b) (m,b) 中,最小的 m m m

样例 #1

样例输入 #1

2 
4 
3 19 10 6 
5 
11 29 13 19 17

样例输出 #1

2   
5

提示

在第一组数据中,货币系统 ( 2 , [ 3 , 10 ] ) (2, [3,10]) (2,[3,10]) 和给出的货币系统 ( n , a ) (n, a) (n,a) 等价,并可以验证不存在 m < 2 m < 2 m<2 的等价的货币系统,因此答案为 2 2 2。 在第二组数据中,可以验证不存在 m < n m < n m<n 的等价的货币系统,因此答案为 5 5 5

【数据范围与约定】

算法自学__背包动态规划_第1张图片

对于 100 % 100\% 100% 的数据,满足 1 ≤ T ≤ 20 , n , a [ i ] ≥ 1 1 ≤ T ≤ 20, n,a[i] ≥ 1 1T20,n,a[i]1

思路

给定的货币系统 ( n , a ) (n, a) (n,a),判断在 a a a中有哪些面额可以用其他的面额凑出来,删除掉这些面额后,剩下的面额数量即为最小的 m m m

这个题目本质上是一个完全背包。先将 a a a中面额从小到大排序。设状态dp[i][j]表示:用前i种面额,凑出金额j的所有方案中,用到最多的货币数。显然,如果一个货币面额j,其dp[n][j] > 1,则该面额可以被其他货币凑出;如果dp[n][j] == 1,则该面额不能被其他货币凑出。容易得到,状态转移方程为:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − a [ i ] ] + 1 ) dp[i][j] = {\rm{max}}(dp[i-1][j], dp[i-1][j-a[i]]+1) dp[i][j]=max(dp[i1][j],dp[i1][ja[i]]+1)

注意边界条件:
d p [ 0 ] [ j ] = { − i n f , j ≠ 0 0 , j = 0 dp[0][j] = \begin{cases} -inf, &j\ne 0\\ 0, &j=0 \end{cases} dp[0][j]={inf,0,j=0j=0
确保所有符合条件的状态都由 dp[0][0] 转移而来。

代码

#include
using namespace std;

const int maxn = 105;

int T, n;
int M;
int a[maxn];
//滚动数组
int dp[25005];
int ans;

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		M = -1;
		ans = 0;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			M = max(M, a[i]);
		}
		//边界条件
		memset(dp, 0x80, sizeof(dp));
		dp[0] = 0;
		for(int i=1;i<=n;i++){
			for(int j=a[i];j<=M;j++){
				dp[j] = max(dp[j], dp[j-a[i]]+1);
			}
		}
		for(int i=1;i<=n;i++){
			//统计不能凑出的货币面额的数量
			if(dp[a[i]]==1) ans++;
		}
		cout<<ans<<'\n';
	}
	return 0;
}

例2 P2946 [USACO09MAR]Cow Frisbee Team S

题目描述

老唐最近迷上了飞盘,约翰想和他一起玩,于是打算从他家的 N N N 头奶牛中选出一支队伍。

每只奶牛的能力为整数,第 i i i 头奶牛的能力为 R i R_i Ri 。飞盘队的队员数量不能少于 1 1 1、大于 N N N。一支队伍的总能力就是所有队员能力的总和。

约翰比较迷信,他的幸运数字是 F F F ,所以他要求队伍的总能力必须是 F F F 的倍数。请帮他

算一下,符合这个要求的队伍组合有多少?由于这个数字很大,只要输出答案对 1 0 8 10^8 108 取模的值。

输入格式

第一行:两个用空格分开的整数: N N N F F F

第二行到 N + 1 N+1 N+1 行:第 i + 1 i+1 i+1 行有一个整数 R i R_i Ri ,表示第 i i i 头奶牛的能力。

输出格式

第一行:单个整数,表示方案数对 1 0 8 10^8 108 取模的值。

样例 #1

样例输入 #1

4 5 
1 
2 
8 
2

样例输出 #1

3

提示

数据范围与约定

  • 对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2000 1 \le N \le 2000 1N2000 1 ≤ F ≤ 1000 1 \le F \le 1000 1F1000 1 ≤ R i ≤ 1 0 5 1 \le R_i \le 10^5 1Ri105

思路

这道题实质上是01背包。定义状态dp[i][j]表示:仅考虑前i头牛,凑出总能力值在模F的条件下为j方案数。显然,这道题目的最终答案对应了状态dp[N][0]。由于我们在选择牛时,仅需要考虑总能力模F下的值,所以我们可以在处理输入时,可以令所有的牛的能力值模F

状态转移方程为:
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j + F − a [ i ] ] dp[i][j] = dp[i-1][j] + dp[i-1][j+F-a[i]] dp[i][j]=dp[i1][j]+dp[i1][j+Fa[i]]
解释:对于我们当前遍历的牛i,其在一个方案中:

  • 不选,对应dp[i-1][j],即用前i-1头牛凑出模Fj的总能力值。
  • 选,对应dp[i-1][j+F-a[i]],即加上这头牛后,凑出模Fj的总能力值。

对于边界条件,我们可以首先初始化第1头牛的情形:

  • 不选,总能力值为0,dp[1][0] = 1。注意到,这种情形是不合理的,所以在最终的答案中需要减去。
  • 选,总能力值为a[i],dp[1][a[i]] = 1

代码

#include
using namespace std;

const int p = 1e8;
const int maxn = 2005;
const int maxf = 1005;

int dp[2][maxf];
int N, F;
int a[maxn];
int cnt = 0;

int main(){
	cin>>N>>F;
	for(int i=1;i<=N;i++){
		cin>>a[i];
		a[i] = a[i]%F;
	}
	dp[cnt^1][a[1]] = 1;
	dp[cnt^1][0] = 1;
	for(int i=2;i<=N;i++){
		for(int j=0;j<F;j++){
			dp[cnt][j] = (dp[cnt^1][j]+dp[cnt^1][(j+F-a[i])%F])%p;
		}
		cnt = cnt^1;
	}
	cout<<dp[cnt^1][0]-1;
	return 0;
}

例3 P1156 垃圾陷阱

垃圾陷阱

题目描述

卡门――农夫约翰极其珍视的一条 Holsteins 奶牛――已经落了到 “垃圾井” 中。“垃圾井” 是农夫们扔垃圾的地方,它的深度为 D D D 2 ≤ D ≤ 100 2 \le D \le 100 2D100)英尺。

卡门想把垃圾堆起来,等到堆得高度大等于于井的深度时,她就能逃出井外了。另外,卡门可以通过吃一些垃圾来维持自己的生命。

每个垃圾都可以用来吃或堆放,并且堆放垃圾不用花费卡门的时间。

假设卡门预先知道了每个垃圾扔下的时间 t t t 1 ≤ t ≤ 1000 1 \le t \le 1000 1t1000),以及每个垃圾堆放的高度 h h h 1 ≤ h ≤ 25 1 \le h \le 25 1h25)和吃进该垃圾能维持生命的时间 f f f 1 ≤ f ≤ 30 1 \le f \le 30 1f30),要求出卡门最早能逃出井外的时间,假设卡门当前体内有足够持续 10 10 10 小时的能量,如果卡门 10 10 10 小时内(不含 10 10 10 小时,维持生命的时间同)没有进食,卡门就将饿死。

输入格式

第一行为两个整数, D D D G G G 1 ≤ G ≤ 100 1 \le G \le 100 1G100), G G G为被投入井的垃圾的数量。

第二到第 G + 1 G+1 G+1 行每行包括三个整数: T T T 1 ≤ T ≤ 1000 1 \le T \le 1000 1T1000),表示垃圾被投进井中的时间; F F F 1 ≤ F ≤ 30 1 \le F \le 30 1F30),表示该垃圾能维持卡门生命的时间;和 H H H 1 ≤ H ≤ 25 1 \le H \le 25 1H25),该垃圾能垫高的高度。

输出格式

如果卡门可以爬出陷阱,输出一个整数,表示最早什么时候可以爬出;否则输出卡门最长可以存活多长时间。

样例 #1

样例输入 #1

20 4
5 4 9
9 3 2
12 6 10
13 1 1

样例输出 #1

13

思路

这道题可以用01背包的思路解决。定义状态dp[i][j]表示:仅考虑前i个垃圾,奶牛到达高度j最长的存活时间

对于一个垃圾i,我们有吃或不吃两种选择:

  • 吃,则状态转移方程为:
    d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] ,   d p [ i − 1 ] [ j ] + t r a s h [ i ] . f ) dp[i][j] = {\rm max}(dp[i][j],\ dp[i-1][j]+trash[i].f) dp[i][j]=max(dp[i][j], dp[i1][j]+trash[i].f)
    可以看出,状态dp[i][j]由状态dp[i-1][j]转移而来。为了使这个转移合法,必须保证奶牛在状态dp[i-1][j]的最长存活时间足够支撑它吃到垃圾i即:
    d p [ i − 1 ] [ j ] ≥ t r a s h [ i ] . t dp[i-1][j] \ge trash[i].t dp[i1][j]trash[i].t
  • 不吃,则状态转移方程为:
    d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] ,   d p [ i − 1 ] [ j − t r a s h [ i ] . h ] ) dp[i][j] = {\rm max}(dp[i][j],\ dp[i-1][j-trash[i].h]) dp[i][j]=max(dp[i][j], dp[i1][jtrash[i].h])
    可以看出,状态dp[i][j]由状态dp[i-1][j-trash[i].h]转移而来。为了使这个转移合法,必须保证奶牛在状态dp[i-1][j-trash[i].h]的最长存活时间足够支撑它吃到垃圾i,且j-trash[i].h >= 0,即:
    ( d p [ i − 1 ] [ j ] ≥ t r a s h [ i ] . t )   & &   ( j − t r a s h [ i ] . h ≥ 0 ) (dp[i-1][j] \ge trash[i].t)\ \&\&\ (j-trash[i].h\ge 0) (dp[i1][j]trash[i].t) && (jtrash[i].h0)

考虑初始状态:
d p [ 0 ] [ j ] = { 10 , j = 0 − i n f , j ≠ 0 dp[0][j]= \begin{cases} 10,&j=0\\ -inf,&j\ne0 \end{cases} dp[0][j]={10,inf,j=0j=0
所以,我们可以将dp[][]中的每个元素初始化为-inf,然后再令dp[0][0]10

代码

#include
using namespace std;

const int maxn = 105;
const int inf = 0x7f7f7f7f;

struct TRASH{
	int t;
	int f;
	int h;
	bool operator<(const TRASH& a)const{
		return t<a.t;
	}
};

int D, G;
TRASH trash[maxn];
int dp[maxn][maxn];
int maxh=-1, maxt=-1; 
int ans = 0x7f7f7f7f;

int main(){
	cin>>D>>G;
	for(int i=1;i<=G;i++){
		cin>>trash[i].t>>trash[i].f>>trash[i].h;
	}
	sort(trash+1, trash+1+G);
	memset(dp, 0x80, sizeof(dp));
	dp[0][0] = 10;
	for(int i=1;i<=G;i++){
		for(int j=0;j<=D;j++){
			//吃 
			if(dp[i-1][j]>=trash[i].t){
				dp[i][j] = max(dp[i][j], dp[i-1][j]+trash[i].f);
			}
			//不吃
			if(j>=trash[i].h && dp[i-1][j-trash[i].h]>=trash[i].t){
				dp[i][j] = max(dp[i][j], dp[i-1][j-trash[i].h]);
			} 
		}
	}
	for(int i=1;i<=G;i++){
		for(int j=0;j<=D;j++){
			if(dp[i][j]>=0){
				maxh = max(maxh, j);
				if(maxh>=D){
					ans = min(ans, trash[i].t);
				}
			}
			maxt = max(maxt, dp[i][j]);
		}
	}
	if(maxh>=D) cout<<ans;
	else cout<<maxt;
	return 0;
}

你可能感兴趣的:(动态规划,算法)