动态规划基础

动态规划

(1)引入:斐波纳契数列F(n)

求解斐波那契数列有递归和递归两种方法
1.递归
递归存在的问题是太慢了效率底下。为什么呢?是因为递归时会存在求解多次相同子问题
比如当N=4时,需要求解3和2的值,但是3又再一次的求解了一次2的值.出现了重复的子问题。导致效率低下。
解决办法:用空间换时间

就是我把前面算的值,我记录下来,这样我就避免了重复计算。

int  f(int n)
2 { 
3        if (a[n]) return a[n];
4       return (a[n] =f(n-1) + f(n-2)) 

这就是记忆化搜素,记忆化搜索就包含动态规划的基本思想

(2)动规的定义:

动态规划是解决多阶段决策过程最优化问题的一种方法。
阶段:
把问题分成几个相互联系的有顺序的几个环节,这些环节即称为阶段。
状态:
某一阶段的出发位置称为状态。通常一个阶段包含若干状态。
决策:
从某阶段的一个状态演变到下一个阶段某状态的选择。
策略:
由开始到终点的全过程中,由每段决策组成的决策序列称为全过程策略,简称策略。
状态转移方程:
前一阶段的终点就是后一阶段的起点,前一阶段的决策选择导出了后一阶段的状态,这种关系描述了由i阶段到i+1阶段状态的演变规律,称为状态转移方程。

(3)动态规划适用的基本条件

1.具有相同子问题
将一个问题分解为A,B,C三个部分,那么这A,B,C分别也能被分解为A,B,C三个部分,而不能是D,E,F三个部分。
2.满足最优子结构
问题的最优解包含着它的子问题的最优解。
此前的状态是基于上一次状态的最优决策
比如A,B,C三个状态,B的状态是由A的状态产生的最优决策,C的状态是有B的状态产生的最优决策,且A的状态是不能影响C的。
3.只能通过当前状态影响
过去的步骤只能通过当前状态影响未来的发展,当前的状态是历史的总结

一·结合原问题和子问题确定状态:
(一维描述不完就二维,二维不行就三维四维……总之要敢想)
状态的参数一般有
1)描述位置的:前(后)i单位,第i到第j单位,坐标为(i,j)等
2)描述数量的:取i个,不超过i个,至少i个等
3)描述对后有影响的:状态压缩的,一些特殊的性质
二 ·确定转移方程
检查参数是否足够;
2)分情况:最后一次操作的方式,取不取,怎么样放,前一项是什么
3)初始边界是什么。
4)注意无后效性。比如说,求A就要求B,求B就要求C,而求C就要求A,这就不符合无后效性了。
三·考虑需不需优化
四·确定编程实现方式
1)递推
2)记忆化搜索

经典动规

最长上升序列

设有一个正整数的序列:b1,b2,…,bn,对于下标i1<i2<…<im,若有bi1<bi2<…<bim
则称存在一个长度为m的上升序列。
例如,下列数列
13 7 9 16 38 24 37 18 44 19 21 22 63 15
对于下标i1=1,i2=4,i3=5,i4=9,i5=13,满足13<16<38<44<63,则存在长度为5的上升序列。
但是,我们看到还存在其他的上升序列: i1=2,i2=3,i3=4,i4=8,i5=10,i6=11,i7=12,i8=13,满足:7<9<16<18
<19<21<22<63,则存在长度为8的上升序列。
问题为:当b1,b2,…,bn给出之后,求出最长的上升序列。

f数组:记录当前的已存在的最长的上升序列的长度

如长度为12的序列
a[i]  13 7 9 16 38 24 37 18 44 19 21  22
f[i]   1 1 2  3  4  4  5  4  6  5  6   7


#include<cstdio>
#include<iostream>
#include<string>
using namespace std;
const int maxn=100;
int a[maxn];
int f[maxn];
int n;

void printArray(int array[],string name){
  printf("%s: ",name.c_str());
  for(int i=0;i<n;i++){
    printf("%d  ", array[i]);
  }
  printf("\n");
}

int main(){
  while(cin>>n){
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=0;i<n;i++){
      f[i]=1;   //每个f[i]的初始值为1
      for(int j=0;j<i;j++){   //遍历当前位置前面的序列
        if(a[j]<a[i]&&f[j]+1>f[i])  //如果前面的某个元素满足,则更改当前元素的f值  
          f[i]=f[j]+1;
      }
    }
    printArray(a,"a[i]");
    printArray(f,"f[i]");
  }
}

每个元素的 f 值都只用找到前面元素里最长的那个序列长度+1
所以 i就是阶段,if()里的内容是决策,f的值就是状态。每个阶段找到前面的最优状态,然后+1
上述例子中,每个 f 的值都是满足当前时刻下标为 i 的 f[i] 是最优解

背包问题

(01背包、完全背包、多重背包)

装箱问题 (简化的01背包)

有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数)。
要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入描述:一个整数v,表示箱子容量;一个整数n,表示有n个物品;接下来n个整数,分别表示这n 个物品的各自体积
输出描述:一个整数,表示箱子剩余空间
样例输入:
24 6
8 3 12 7 9 7
样例输出:
0

这个例子没有物品的权值,求得只是剩余空间最少

确定状态转移方程
dp[i][j]:表示前i个物品在容量为j的背包里可以装下的最大体积。
i表示1~n物品  j表示1~V的容量
枚举最后一次决策——————第i个物品放还是不放!
不放:dp[i][j] = dp[i-1][j](如果不放当前物品,我当前的最大体积就是我前一个为当前容量j时的dp(最大体积))
放: dp[i][j]=dp[i-1][j-v[i]]+v[i];(如果放,当前的最大体积就是我的当前背包容量减去当前物品容量的体积的dp值 + 这个物品的体积。)

初值 dp[i][j] = 0;

然后最优解就是我在放与不放里选一个最大的。


for(int i=1;i<=n;i++){
       for(int j=1;j<=m;j++){
         dp[i][j]=dp[i-1][j];
         if(v[i]<=j)
          dp[i][j]=max(dp[i-1][j-v[i]]+v[i],dp[i][j]);
       }
   }

下一个例子:
有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

这个是加上了价值,并且求得是价值总和最大
两种情况:
1.不放当前物品 f[i][j] = f[i-1][j]
2.放当前物品 f[i][j] = f[i-1][j-c[i]]+w[i]

所以 f[i][j]=max{f[i-1][j],f[i-1][j-c[i]]+w[i]}

for(int i=1;i<=n;i++){
       for(int j=1;j<=m;j++){
         dp[i][j]=dp[i-1][j];//不放
         if(v[i]<=j) //放的话,我的前一个状态的dp得存在,也就是j-v[i]是一个大于0的数 所以v[i]<=j
          dp[i][j]=max(dp[i-1][j-v[i]]+w[i],dp[i][j]);
       }
   }

//滚动数组 
滚动数组就是将二维的dp[i-1][j]和dp[i-1][j-v[i]]放在了一个数组里
逆序枚举容量的原因:i的状态是有i-1状态做出决策推导而来的
观察发现,二维里的dp[i-1][j]和dp[i-1][j-v[i]]的纵坐标j和j-v[i]是存在j>j-v[i]的关系的,
所以只有当你逆着走才是先看j再看j-v[i]
也就是说,当你在i这个状态时,你倒着走,你的j-v[i]还是在i-1的结果,然后你再加上当前的w[i]for(int i=1;i<=n;i++){
       for(int j=m;j>=v[i];j--){
          dp[j]=max(dp[j-v[i]]+w[i],dp[j]);
 		}
 }        

完全背包

有n种物品和一个容量为v的背包,每种物品都有无限件可用。放入第i种的物品的体积是vi,价值是wi.求解:将哪些物品装入背包,可使这些物品的体积总和不超过背包容量,且价值总和最大。

状态转移方程
f[i,j]=max(f[i-1,j-kv[i]]+kw[i],f[i-1,j]) (0<=k*v[i]<=j)

//二维dp
for(int i=1;i<=n;i++){
       for(int j=1;j<=v;j++){
         dp[i][j]=dp[i-1][j];  //不放
         if(v[i]<=j)
          dp[i][j]=max(dp[i][j-v[i]]+w[i],dp[i][j]);  //因为可以一个物品可以多次使用,所以放的方程是dp[i][j-v[i]]+w[i],而不是01背包的每个物品只能使用一次的dp[i-1][j-v[i]]+w[i]
       }
   }

//滚动数组
//正序原因
第i次循环时,观察二维dp你会发现dp[i][j-v[i]和dp[i-1][j]      j-v[i]是小于j的 
即第i次循环时,我的容量为j是我要的是i-1次的dp值,j-v[i]我要的是第i次循环值,所以我就正序走,先改变j-v[i]为第i次

for(int i=1;i<=n;i++){
       for(int j=v[i];j<=v;j++){
          dp[j]=max(dp[j-v[i]]+w[i],dp[j]);
          }
}          

01 背包与完全背包的区别
前者就在于每一种种类的物品只能选择一个,而后者则可以选择无数个

多重背包

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000;
int dp[maxn],number[maxn],weight[maxn],value[maxn];
int bag;
void zeroOneBack(int weight,int value){  //01背包
   for(int i=bag;i>=weight;i--){
      dp[i]=max(dp[i-weight]+value,dp[i]);
   }
}

void completeBack(int weight,int value){  //完全背包
   for(int i=weight;i<=bag;i++){
     dp[i]=max(dp[i-weight]+value,dp[i]);
   }
}

void MultipleBack(int number,int weight,int value){  //多重背包
    if(bag<=number*weight){    //转换为完全背包
        completeBack(weight,value);
        return ;
    }else{   //否则转换为01背包
       int k=1;
       while(k<=number){
          zeroOneBack(k*weight,k*value);
          number=number-k;
          k=k*2;
       }
       zeroOneBack(number*weight,number*value);
    }
}

int main(){
   int n;
   while(cin>>bag>>n){
     memset(dp,0,sizeof(dp));
      for(int i=1;i<=n;i++){
          cin>>number[i]>>weight[i]>>value[i];
      }
      for(int i=1;i<=n;i++){
          MultipleBack(number[i],weight[i],value[i]);
      }
      cout<<dp[bag]<<endl;
   }
}

例题

问题 A: 小辉辉玩积木

题目描述
渣渣辉有个儿子叫小辉辉,他非常喜欢数论。
有一天,小辉辉在玩积木时,对渣渣辉提出了一个问题:

在2×n的一个长方形方格中用一个1×2的积木铺满方格输入n 输出铺放方案的总数.

例如n=3时为2×3方格,积木的铺放方案有三种如下图:
在这里插入图片描述

输入
输入数据由多行组成,每行包含一个整数n表示该测试实例的长方形方格的规格是2×n (0<n<=50)。
输出
对于每个测试实例,请输出铺放方案的总数,每个实例的输出占一行。
样例输入
1
3
2
样例输出
1
3
2


#include<stdio.h>
#include<stdlib.h>
double fun(int N)
{
	int i;
	double f1 = 1, f2 = 2,f3;
	if (N == 1)
		return 1;
	if (N == 2)
		return 2;
	else {
		for (i = 1; i <= N-2; i++)
		{
			f3 = f1 + f2;
			f1 = f2;
			f2 = f3;

		}
		return f3;
	}
}
int main()
{ 
	int N;
	while(scanf("%d", &N)!=EOF)
	printf("%.0lf\n",fun(N));
	return 0;
}

//记忆化搜索
#include<bits/stdc++.h>
using namespace std;

long long a[60] = {1, 2};//赋初值
long long f(int n) {
    if (a[n])
        return a[n];
    return a[n] = f(n - 2) + f(n - 1);
}

int main()
{
    int N;
    while (cin >> N) {
        cout << f(N - 1) << endl;
    }
    return 0;

    return 0;
}

问题 B: 入侵和反击
题目描述
A国部署的反导系统遇到了一个致命BUG,那就是每一次发射的拦截导弹的飞行高度都将只能小于等于上一枚导弹的飞行高度,第一次发射的拦截导弹的飞行高度可以看作是足够大。对于A国,这是一件很严重的问题,这意味着A国的防空系统面临空前危机。
通过对A国的军事部门计算机的入侵,A国还不知道敌对国B国刚才已经发现了这项BUG。更不知道,在这项BUG的报告书上交到B国空军司令部那一刻,三分钟后B国的全体高级空军军官已经在作战室讨论作战方案。
如果战争真的开始,B国将依次派出n架战斗机A国将依次发射拦截导弹,这n架飞机的飞行高度分别是h1h2h3…hn。B国将要充分利用这项漏洞,考虑到这么一种情况,假设只要A国的导弹的飞行高度大于等于B国飞机就能百分之百地锁定并击落,那么B国,最少将会有几架不被击落飞机?
输入
第一行为T,表示有T组输入数据(T<200)。
每组数据第一行是n代表有n架飞机(1=<n<=20 000)。
接下来一行有n个数,分别代表n架飞机的飞行高度,飞机飞行高度maxh为(1<=maxh<=50 000)。
输出
对于每组测试数据,在每行中输出一个数。表示B国最少将会有几架未被击落飞机。

样例输入
2
1
1000
6
340 260 101 405 278 89
样例输出
0
2

最大下降子序列
#include<bits/stdc++.h>
using namespace std;
const int maxn=20000+5;
int a[maxn],dp[maxn];

int main(){
  int t,n;
  cin>>t;
  while(t--){
    memset(a,0,sizeof(a));
    cin>>n;
    //memset(f,0,sizeof(f));//可以赋值0,-1,0x3f3f3f3f;具体赋值什么依照题意而定
    fill(dp,dp+n,1);
    for(int i=0;i<n;i++)
       cin>>a[i];
     for(int i=0;i<n;i++)
         for(int j=0;j<i;j++){
            if(a[j]>=a[i]&&dp[j]+1>dp[i])
                dp[i]=dp[j]+1;
         }
      cout<<n-dp[n-1]<<endl;                
  }
}

问题 C: 红红数钞票

时间限制: 1 Sec 内存限制: 128 MB
题目描述
喵星发布了一种新型钞票,这种钞票有正有负,给定一个长度为k的钞票序列,然后红红通过数钞票来获得钞票,但是红红有一个怪癖,他仅仅只是拥有连续的记忆,意思就是他只能连续计数,但是红红想要获得尽可能多的钱,但是红红做不到。你可以帮帮红红吗?
举个例子,给定钞票序列是{-2 11 -4 13 -5 -2} 而红红去数的最大子序列就是就是{11 -4 13 }
所以红红能够获得最多钱为20。
现在请你来帮助红红早日发家致富。

输入
每一个样例包含一组数据。每一组数据包含两行。

第一行包含一个正整数k(<=10000)。

第二行包含K个数,每一个数中间用空格隔开。

输出
对于每一个样例输出,都输出一行。
这一行的第一个数字就是最大的和
后面两个数 就是这个钞票序列的起始的数i和 终止的数
这些数字必须以空格相隔开,并且在每一行的末尾没有额外的空格。
另外 可能这个最大的钞票序列往往并不是唯一的,所以只需要输出包含最小的起始坐标或者终止坐标。
注意 如果所有的数全部都是负数,那么其的最大钞票和为0,且你应该输出的是从这整个序列第一个数到最后一个数。
样例输入
10
-10 1 2 3 4 -5 -23 3 7 -21
样例输出
10 1 4

动态规划
 这里每一次相加的和,如何保证后面加了负数之后,再次加上一个正数不被影响
   比如 1 2 -3 8
  1 2 -3 正好相加为0  但是题目中要求的 序列 1 2 -3 8 作为最后的结果序列

#include<bits/stdc++.h>
using namespace std;
const int maxn=10000+5;
int num[maxn];
int main(){
  int n;
   cin>>n;
   for(int i=0;i<n;i++)
      cin>>num[i];
    int left=0;    //真正的左下标
    int right=0;    //真正的右下标
    int templeft=0;   //临时的左下标
    int tempright=0;  //临时的右下标
    int sum=0;
    int max=0;
      for(int i=0;i<n;i++){
        sum+=num[i];
        if(sum<0){    //出现负数置零
           sum=0;
           templeft=i+1;  //临时的左下标为求和小于零的下一个坐标 
        }else if(sum>max){ 
           right=i;    
           max=sum;
           left=templeft;
        }
      }
      if(max==0)
         cout<<0<<" "<<num[0]<<" "<<num[n-1]<<endl;
      else
         cout<<max<<" "<<num[left]<<" "<<num[right]<<endl;

}

问题 D: Charm Bracelet

题目描述
Bessie has gone to the mall’s jewelry store and spies a charm bracelet. Of course she’d like to fill it with the best charms possible from the N (1 ≤ N ≤ 3402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400) a ‘desirability’ factor Di (1 ≤ Di ≤ 100) and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12880).
Given that weight limit as a constraint and a list of the charms with their weights and desirability rating deduce the maximum possible sum of ratings.
输入

  • Line 1: Two space-separated integers: N and M
  • Lines 2…N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di
    输出
  • Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints
    样例输入
    4 6
    1 4
    2 6
    3 12
    2 7
    样例输出
    23

翻译:
题目描述
贝茜已经去了商场的珠宝店,并间谍一条手链。当然,她想用N(1≤N≤3402)可用的魅力填充它最好的魅力。提供的列表中的每个魅力i具有权重Wi(1≤Wi≤400)和’期望’因子Di(1≤Di≤100)并且可以最多使用一次。Bessie只能支撑重量不超过M(1≤M≤12880)的手链。
鉴于作为约束的重量限制和具有其权重和合意性评级的魅力列表推导出最大可能的评级总和。
输入
*第1行:两个以空格分隔的整数:N和M
*第2…N + 1行:第i + 1行描述具有两个以空格分隔的整数的魅力i:Wi和Di
输出
*第1行:一个整数,它是在权重限制下可以实现的最大魅力可取性总和
样例输入
4 6
1 4
2 6
3 12
2 7
样例输出
23

01背包问题
#include<bits/stdc++.h>
using namespace std;
const int maxn=13000;
int w[maxn],d[maxn],dp[maxn];

int main(){
   int n,m;
   cin>>n>>m;
     for(int i=1;i<=n;i++){
       cin>>w[i]>>d[i];
     }
   for(int i=1;i<=n;i++){
       for(int j=m;j>=w[i];j--){
       dp[j]=max(dp[j],dp[j-w[i]]+d[i]);
     }
   }
   cout<<dp[m]<<endl;
 }

问题 E: 红红绝地求生

时间限制: 1 Sec 内存限制: 128 MB
题目描述
红红最近迷上了吃鸡,房间有n个配件,每一个配件的重量为c(<=1e3),和价值为v(v <= 1e3)假设红红刚落地就捡到一个三级包,容量为S,请问红红可以成为多肥的快递员?
输入
输入的第一行为T, 表示红红要跳T次伞。
每一组数据由三行组成。
第一行包含两个整数n s,分别表示数量和背包容量,第二行包含n个整数,表示每一个配件的价值。
第三行包含n个整数,表示每一个配件重量。
输出
对于每一组数据,输出红红可以多肥。
样例输入
1
10 10
1 3 5 7 9 11 13 15 17 19
19 17 15 13 11 9 7 5 3 1
样例输出
51

01背包
#include<bits/stdc++.h>
using namespace std;
const int maxn=13000;
int w[maxn],d[maxn],dp[maxn];

  int main(){
     int n,m,t;
     cin>>t;
     while(t--){
          cin>>n>>m;
          memset(w,0,sizeof(w));
          memset(d,0,sizeof(d));
          memset(dp,0,sizeof(dp));
          for(int i=1;i<=n;i++){
              cin>>w[i];
            }
            for(int i=1;i<=n;i++){
                cin>>d[i];
              }
          for(int i=1;i<=n;i++){
              for(int j=m;j>=w[i];j--){
                  dp[j]=max(dp[j],dp[j-w[i]]+d[i]);
                }
          }
     cout<<dp[m]<<endl;
   }
 }

问题 F: 一卡通

题目描述
学校食堂,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
输入
多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。
n=0表示数据结束。

输出
对于每组输入输出一行包含一个整数,表示卡上可能的最小余额。
样例输入
1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
0
样例输出
-45
32

01背包
#include<bits/stdc++.h>
using namespace std;
const int maxn=10000;
int w[maxn],dp[maxn];

int main(){
  int n,last,sum;
  while(cin>>n){
    sum=0;
    memset(w,0,sizeof(w));
    memset(dp,0,sizeof(dp));
    if(n==0) break;
     for(int i=1;i<=n;i++){
       cin>>w[i];
     }
    cin>>last;
    sort(w+1,w+1+n);
    if(last>=5){
        for(int i=1;i<n;i++){     //先找到一个最贵的菜呗,最后在快没钱的时候再买它。所以先排个序
            for(int j=last-5;j>=w[i];j--)
                dp[j]=max(dp[j],dp[j-w[i]]+w[i]);
        }
            cout<<last-dp[last-5]-w[n]<<endl;//最后再减去最贵的菜
    }
    else cout<<last<<endl;
  }
}

问题 H: Piggy-Bank

题目描述
Before ACM can do anything a budget must be prepared and the necessary financial support obtained. The main income for this action comes from Irreversibly Bound Money (IBM). The idea behind is simple. Whenever some ACM member has any small money he takes all the coins and throws them into a piggy-bank. You know that this process is irreversible the coins cannot be removed without breaking the pig. After a sufficiently long time there should be enough cash in the piggy-bank to pay everything that needs to be paid.
But there is a big problem with piggy-banks. It is not possible to determine how much money is inside. So we might break the pig into pieces only to find out that there is not enough money. Clearly we want to avoid this unpleasant situation. The only possibility is to weigh the piggy-bank and try to guess how many coins are inside. Assume that we are able to determine the weight of the pig exactly and that we know the weights of all coins of a given currency. Then there is some minimum amount of money in the piggy-bank that we can guarantee. Your task is to find out this worst case and determine the minimum amount of cash inside the piggy-bank. We need your help. No more prematurely broken pigs!

输入
The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing two integers E and F. They indicate the weight of an empty pig and of the pig filled with coins. Both weights are given in grams. No pig will weigh more than 10 kg that means 1 <= E <= F <= 10000. On the second line of each test case there is an integer number N (1 <= N <= 500) that gives the number of various coins used in the given currency. Following this are exactly N lines each specifying one coin type. These lines contain two integers each Pand W (1 <= P <= 50000 1 <= W <=10000). P is the value of the coin in monetary units W is it’s weight in grams.
输出
Print exactly one line of output for each test case. The line must contain the sentence “The minimum amount of money in the piggy-bank is X.” where X is the minimum amount of money that can be achieved using coins with the given total weight. If the weight cannot be reached exactly print a line “This is impossible.”.
样例输入
3
10 110
2
1 1
30 50
10 110
2
1 1
50 30
1 6
2
10 3
20 4
样例输出
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.

翻译
题目描述
在ACM可以做任何事情之前,必须准备预算并获得必要的财务支持。此行动的主要收入来自不可逆转的捆绑资金(IBM)。背后的想法很简单。每当一些ACM成员有任何小钱时,他就拿走所有硬币并将它们扔进存钱罐。你知道这个过程是不可逆转的,硬币不能在不打破猪的情况下被移除。经过足够长的时间后,存钱罐中应该有足够的现金来支付需要支付的所有费用。
但是存钱罐存在很大问题。无法确定内部有多少钱。因此,我们可能会将猪分成碎片,但却发现没有足够的钱。显然,我们希望避免这种不愉快的情况。唯一的可能性是称重存钱罐并试图猜测里面有多少硬币。假设我们能够确切地确定猪的重量并且我们知道给定货币的所有硬币的重量。然后,我们可以保证在存钱罐中有一些最低金额。你的任务是找出最坏的情况,并确定存钱罐内的最低现金数量。我们需要你的帮助。不再过早破猪!

输入
输入由T个测试用例组成。它们的数量(T)在输入文件的第一行给出。每个测试用例都以包含两个整数E和F的行开头。它们表示空猪和装满硬币的猪的重量。两种重量均以克为单位。没有猪的体重超过10公斤,这意味着1 <= E <= F <= 10000.在每个测试用例的第二行,有一个整数N(1 <= N <= 500)给出各种数量以给定货币使用的硬币。在此之后正好是N行,每行指定一种硬币类型。这些行包含两个整数,每个Pand W(1 <= P <= 50000 1 <= W <= 10000)。P是以货币单位表示的硬币值W是以克为单位的重量。
输出
为每个测试用例打印一行输出。该行必须包含句子“存钱罐中的最小金额是X.” 其中X是使用给定总重量的硬币可以实现的最小金额。如果无法达到重量,请准确打印一行“这是不可能的”。

完全背包
#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
int p[maxn],w[maxn],dp[maxn];

int main(){
   int n,t,e,f;
   cin>>t;
   while(t--){
     memset(p,0,sizeof(p));
     memset(w,0,sizeof(w));
     memset(dp,0x3f3f3f3f,sizeof(dp));  //因为是求最小,所以初值赋为最大
       cin>>e>>f;
       cin>>n;
     for(int i=1;i<=n;i++){
       cin>>p[i]>>w[i];
     }
     dp[0]=0;//令dp[0]=0不然就没结果了 
     for(int i=1;i<=n;i++){
        for(int j=w[i];j<=f-e;j++){
             dp[j]=min(dp[j],dp[j-w[i]]+p[i]);
     }
   }
   if(dp[f-e]!=0x3f3f3f3f)
        printf("The minimum amount of money in the piggy-bank is %d.\n",dp[f-e]);
    else
      printf("This is impossible.\n");

  }
 return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值