zoukankan      html  css  js  c++  java
  • 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
    ",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("
    ");
    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("
    ");        
    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
    ", 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 }
  • 相关阅读:
    idea.vmoption文件修改之后,Idea无法打开的问题
    py学习:namedtuple 具名元组
    py学习:可变对象作为函数参数默认值
    图解Python变量与赋值(转)
    github 提交的认证方式
    让 IDEA 忽略某个文件夹的方式
    在 Windows 上开启 telnet 功能
    Java原生日志 Java.util.logging
    转:Python简史
    Maven无法下载fastdfs-client-java依赖,Dependency 'org.csource:fastdfs-client-java:1.27-SNAPSHOT' not found.
  • 原文地址:https://www.cnblogs.com/jiamian/p/10754103.html
Copyright © 2011-2022 走看看