DP基础练习(4.21)

数塔

Description 

在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:

有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

Input

 

输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。

 

Output

 

对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。

 Sample Input 1

1
5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

Sample Output 1

30

 

 

 1 #include <cstdio>
 2 #include <algorithm> 
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     int c;
 9     scanf("%d",&c);
10     while(c--)
11     {
12         int a[101][101]={0};
13         int dp[101][101]={0};
14         int n;
15         scanf("%d",&n);
16         for(int i=1;i<=n;i++)
17         {
18             for(int j=1;j<=i;j++)
19             {
20                 scanf("%d",&a[i][j]);
21             }
22         }
23         for(int i=1;i<=n;i++)
24         {
25             dp[n][i]=a[n][i];
26         }
27         for(int i=n-1;i>=1;i--)
28         {
29             for(int j=1;j<=i;j++)
30             {
31                 dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
32             }
33         }
34         printf("%d\n",dp[1][1]);
35     } 
36     return 0;
37 }

 

 

音量调节

Description

一个吉他手准备参加一场演出。他不喜欢在演出时始终使用同一个音量,所以他决定每一首歌之前他都要改变一次音量。在演出开始之前,他已经做好了一个列表,里面写着在每首歌开始之前他想要改变的音量是多少。每一次改变音量,他可以选择调高也可以调低。
音量用一个整数描述。输入文件中给定整数beginLevel,代表吉他刚开始的音量,以及整数maxLevel,代表吉他的最大音量。音量不能小于0也不能大于maxLevel。输入文件中还给定了n个整数c1,c2,c3…..cn,表示在第i首歌开始之前吉他手想要改变的音量是多少。
吉他手想以最大的音量演奏最后一首歌,你的任务是找到这个最大音量是多少。

Input

第一行依次为三个整数:n, beginLevel, maxlevel。
第二行依次为n个整数:c1,c2,c3…..cn。

Output

输出演奏最后一首歌的最大音量。如果吉他手无法避免音量低于0或者高于maxLevel,输出-1。

Sample Input

3 5 10 
5 3 7

Sample Output

10

HINT

 

1<=N<=50,1<=Ci<=Maxlevel 1<=maxlevel<=1000

0<=beginlevel<=maxlevel

 

Source

一开始便开始想用f[i]表示前i个物品能够得到的最大的音量,以为和装箱问题一样。于是借用一下强大的搜索引擎,发现一种叫布尔型dp,就是用f[i][j]表示前i个物品在音量为j时可行;记住这种表示方式;

 1 #include <iostream>
 2 #include <cstring> 
 3 #include <algorithm> 
 4 
 5 using namespace std;
 6 
 7 int a[1010];
 8 bool dp[55][1010];
 9 
10 int main()
11 {
12     int n,initial,maxl;
13     cin>>n>>initial>>maxl;
14     for(int i=1;i<=n;i++)
15     {
16         cin>>a[i];
17     }
18     memset(dp,false,sizeof(dp));
19     dp[0][initial]=true;
20     for(int i=1;i<=n;i++)
21     {
22         for(int j=0;j<=maxl;j++)
23         {
24             //dp[i][j]是第i次调节可以获得的音量j 
25             dp[i][j]=((j+a[i]<=maxl)&&dp[i-1][j+a[i]])||((j-a[i]>=0)&&dp[i-1][j-a[i]]);
26             //j+a[i]<=maxl和 j-a[i]>=0判断是否超出dp[]的范围
27             //dp[i-1][j+a[i]]为真说明第i次调节后的j可以是第i-1次的某个音量减去a[i]得到 
28             //dp[i-1][j-a[i]]为真说明第i次调节后的j可以是第i-1次的某个音量加上a[i]得到 
29         }
30     }
31     for(int i=maxl;i>=0;i--)
32     {
33         if(dp[n][i])
34         {
35             cout<<i<<endl;
36             return 0;
37         }
38     }
39     cout<<-1<<endl;    
40     return 0;
41 }

 

消失之物

Description
ftiasch 有 N 个物品, 体积分别是 W1, W2, ..., WN。 由于她的疏忽, 第 i 个物品丢失了。 “要使用剩下的 N - 1 物品装满容积为 x 的背包,有几种方法呢?” -- 这是经典的问题了。她把答案记为 Count(i, x) ,想要得到所有1 <= i <= N, 1 <= x <= M的 Count(i, x) 表格。

 

 

Input
 
第1行:两个整数 N (1 ≤ N ≤ 2 × 103) 和 M (1 ≤ M ≤ 2 × 103),物品的数量和最大的容积。

第2行: N 个整数 W1, W2, ..., WN, 物品的体积。

Output
 
一个 N × M 的矩阵, Count(i, x)的末位数字。

Sample Input
3 2
1 1 2
Sample Output
11
11
21
HINT
如果物品3丢失的话,只有一种方法装满容量是2的背包,即选择物品1和物品2。

题解
先考虑不删除物品怎么做,其实就是一个背包,dp[i][j]=dp[i-1][j]+dp[i-1][j-w[i]];那么现在考虑少了物品i会减少几种方案,当x小于w[i]时,i物品一定不会被选上 g[i]=f[i] 当x大于等于w[i]时,i物品可能会被选上,直接求不选的情况比较困难。可以换个思路,设g[x]为不选当前物品的容量为x的方案数,用总方案数-选的方案数得到不选的方案数。总方案数及f[x],不选的方案数可以想为先不选i再最后把i选上,即g[x-w[i]]。

 

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 using namespace std;
 6 const int N=100010;
 7 int f[N],g[N],w[N];
 8 int n,m;
 9 int main(){
10     scanf("%d%d",&n,&m);
11     for(int i=1;i<=n;i++) scanf("%d",&w[i]);
12     
13     f[0]=1;
14     for(int i=1;i<=n;i++){
15         for(int j=m;j>=w[i];j--)
16         f[j]=(f[j]+f[j-w[i]])%10;
17     }
18     for(int i=1;i<=n;i++){
19         for(int j=0;j<=m;j++){
20             if(j<w[i]) g[j]=f[j];
21             else g[j]=(f[j]-g[j-w[i]]+10)%10;
22         }
23         for(int j=1;j<=m;j++) printf("%d",g[j]);
24         printf("\n");
25     }
26     return 0;
27 }

 

分析

很明显是个01背包的dp,但是问题在于统计这个答案上。感觉和容斥原理略有关系?于是研读了一下黄学长的极妙的做法

首先,f[j]表示装满容积为j的背包的方案数 很明显,f[j]+=f[j-w[i]] (f[0]=1)

统计答案我们用c[i][j]表示

再枚举I,j

如果f[j]<w[i],显然,装满容积j显然不可能用到第i个物品,所以c[i][j]=f[j]。

如果f[j]>=w[i],f[j]里就包括了选择了第i件物品的情况,那我们需要扣除掉这一部分。把扣除的这部分转换一下:

用第i件物品填满j ---> 用了其他物品填了j-w[i],所以c[i][j]=f[j]-c[i][j-w[i]]

 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define N 2020
 4 int n,m;
 5 int f[N],w[N];
 6 int    c[N][N];
 7 int main()
 8 {
 9     scanf("%d%d",&n,&m);
10     for(int i=1;i<=n;i++)
11         scanf("%d",&w[i]);
12     f[0]=1;
13     for(int i=1;i<=n;i++)
14         for(int j=m;j>=w[i];j--)
15             f[j]+=f[j-w[i]],f[j]%=10;
16     for(int i=1;i<=n;i++)
17     {
18         c[i][0]=1;
19         for(int j=1;j<=m;j++)
20         {
21             if(j>=w[i])c[i][j]=(f[j]-c[i][j-w[i]]+10)%10;
22             else    c[i][j]=f[j];
23             printf("%d",c[i][j]);
24         }    
25         printf("\n");        
26     }
27     return 0;    
28 }

 

货币系统

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

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

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

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

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

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

输入

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

输出

2
5

说明

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

备注:

1 <= T <= 20, 1 <= n <= 100, 1 <= a[i] <= 25000

 

解题思路
题目说的很长,其实题目的大意就是有 n nn 个数,然后让你找 m mm 个数使得这 m mm 个数与 n nn 个数的表示范围相同。那么我们来分析一下,首先相同的数字肯定是没有意义的,我们对原数组进行去重,然后我们用一个数组 vis[i] vis[i]vis[i] 表示数字 i ii 是否能够表示,如果 vis[i]=true vis[i]=truevis[i]=true 可以表示,否则不可以表示。那么我们就对于 n nn 个数中的每一个数字进行判断,第一个 a[0] a[0]a[0] 肯定不能表示,那么我们结果 ans++ ans++ans++, 然后把 a[0] a[0]a[0] 能够表示的数字 i ii 全标记为 vis[i]=true vis[i]=truevis[i]=true;那么在检查第 i ii 个数字,如果能够用已经标记的数字表示,那么说明这个数可以用 之前的数表示,答案不记录;否则答案记录,而且将已经能够表示的数字加上 a[i] a[i]a[i],也进行标记 vis[j+a[i]]=true vis[j+a[i]]=truevis[j+a[i]]=true,直至循环完毕,输出 ans ansans 即可。

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 4 #include <stdlib.h>
 5 #include <math.h>
 6 #include <set>
 7 #include <vector>
 8 #include <algorithm>
 9 #define pb push_back
10 #define mk make_pair
11 #define fi first
12 #define se second
13 using namespace std;
14 typedef pair<int, int> pii;
15 typedef long long LL;
16 typedef unsigned long long ULL;
17 const int MAXN = 25000+5;
18 int a[105];
19 bool vis[MAXN];
20 int main() {
21     int T; cin>>T;
22     while(T--) {
23         int n; cin>>n;
24         for(int i=0; i<n; i++) cin>>a[i];
25         sort(a, a+n);
26         n = unique(a, a+n)-a;
27         memset(vis, false, sizeof vis);
28         vis[0] = true;
29         int ans = 0;
30         for(int i=0; i<n; i++) {
31             if(!vis[a[i]]) {
32                 ans++;
33                 for(int j=0; j<MAXN; j++) if(j+a[i]<=25000&&vis[j]) vis[j+a[i]] = true;
34             }
35         }
36         cout<<ans<<endl;
37     }
38     return 0;
39 }

题解
首先我想弱弱地说一句,比赛的时候我并没有看懂这道题,也就是说没有思路说多了都是泪

但是我下来想了一下,这道题可以用背包来做

递推式(转移方程)为f[i]=max(f[i],f[i−money[j]]+1)

 f[i]表示i面值最多能被几张钱表示

f[i]=-inf表示有且只有它自己则f[i]=1

在开始之前初始化f[0]=0

就是这样~

 1 #include<iostream>
 2 #include<cstring>
 3 using namespace std;
 4 int a[500], n;
 5 int ans;
 6 int f[50000];
 7 int main() {
 8     //freopen("money.in","r",stdin);
 9     //freopen("money.out","w",stdout);
10     int T;
11     cin >> T;
12     for(int k = 1; k <= T; k++) {
13         memset(f, -10000, sizeof f);
14         ans = 0;
15         cin >> n;
16         for(int i=1; i<=n; i++)
17             cin >> a[i];
18         f[0] = 0;
19         for(int i = 1; i <= n; i++) {
20             for(int j = a[i]; j <= 30000; j++) {
21                 f[j] = max(f[j], f[j - a[i]] + 1);
22             }
23         }
24         for(int i = 1; i <= n; i++)
25             if(f[a[i]] == 1) {
26                 ans++;
27             }
28         cout << ans << endl;
29     }
30 }
31  

一个结论性的题目……吧。

首先手推一下,发现新货币系统中的最小值 m′ m&#x27;m

一定等于原来的货币系统中的最小值 m mm。如果大于,则新货币系统无法表达 m mm;如果小于,则原货币系统无法表达 m′ m&#x27;m

然后我们递归性地猜想:假如我去掉了这个最小值以及最小值能表达的数(因为新货币系统里面如果再有这些数就不够优秀了),那么再选择最小值是否也一定是最优的?
仿照上面的证明可以发现这个推论是正确的。

因此我们就可以得到我们的算法:
(1)找到原货币系统当前的最小值,加入新货币系统。
(2)在原货币系统中删除新货币系统能表达的数。
循环(1),(2)直到原货币系统没有任何数。

我们实现上可以不按这么写。我们可以从小到大枚举原货币系统中的数,判断它能否被新货币系统表达。能则跳过;不能则更新新货币系统。
判断以及更新可以用完全背包来做。

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 const int MAXN = 100;
 5 const int MAXM = 25000;
 6 bool dp[MAXM + 5];
 7 int a[MAXN + 5];
 8 void solve() {
 9     int n, m = 0, lim = 0;
10     scanf("%d", &n);
11     for(int i=1;i<=n;i++) {
12         scanf("%d", &a[i]);
13         lim = max(lim, a[i]);
14     }
15     for(int i=0;i<=lim;i++)
16         dp[i] = false;
17     sort(a+1, a+n+1); dp[0] = true;
18     for(int i=1;i<=n;i++) {
19         if( !dp[a[i]] ) {
20             for(int j=a[i];j<=lim;j++)
21                 dp[j] |= dp[j-a[i]];
22             m++;
23         }
24     }
25     printf("%d\n", m);
26 }
27 int main() {
28     int T;
29     scanf("%d", &T);
30     for(int i=1;i<=T;i++)
31         solve();
32     return 0;
33 }

 

posted @ 2019-04-23 01:13  jiamian22  阅读(223)  评论(0编辑  收藏  举报