目录
4、包子凑数(第八届蓝桥杯省赛C++ A组/B组 & 第八届蓝桥杯省赛Java A组/B组/C组)
背包关于最大值最小值初始化的总结:
(此总结搬运于Acwing彩色铅笔)
1、货币系统(usaco training 2.3)
给定 V 种货币(单位:元),每种货币使用的次数不限。
不同种类的货币,面值可能是相同的。
现在,要你用这 V 种货币凑出 N 元钱,请问共有多少种不同的凑法。
输入格式
第一行包含两个整数 V 和 N。
接下来的若干行,将一共输入 V 个整数,每个整数表示一种货币的面值。
输出格式
输出一个整数,表示所求总方案数。
数据范围
1≤V≤25
1≤N≤10000
答案保证在long long
范围内。
输入样例:
3 10
1 2 5
输出样例:
10
思路:
完全背包问题(一个可以取无数次),取前i种,这里最终面值刚好为j
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=10003;
int kind[28];
LL f[N][N];//取前i种,并且最终面值为j的取法种类
int main()
{
int v,n;
cin>>v>>n;
for(int i=1;i<=v;i++)
{
scanf("%d",&kind[i]);
}
f[0][0]=1;
for(int i=1;i<=v;i++)
for(int j=0;j<=n;j++)
{
f[i][j]+=f[i-1][j];
if(j>=kind[i])f[i][j]+=f[i][j-kind[i]];
}
//cout<<f[2][0];
cout<<f[v][n];
return 0;
}
2、01背包问题(模板)
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
思路:
01背包问题模型
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1003;
int w[N],v[N];
int f[N][N];//选前i种,并且体积不超过j的价值的最大值
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&v[i],&w[i]);
}
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
cout<<f[n][m];
return 0;
}
/*
4 5
1 2
2 4
3 4
4 5
8
*/
3、完全背包问题(模型)
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
思路:
完全背包模型
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1003;
int v[N],w[N];
int f[N][N];//从前i种中取,且总体积不超过j
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&v[i],&w[i]);
}
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=v[i])f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}
cout<<f[n][m];
return 0;
}
/*
4 5
1 2
2 4
3 4
4 5
10
*/
4、包子凑数(第八届蓝桥杯省赛C++ A组/B组 & 第八届蓝桥杯省赛Java A组/B组/C组)
小明几乎每天早晨都会在一家包子铺吃早餐。
他发现这家包子铺有 N 种蒸笼,其中第 i 种蒸笼恰好能放 Ai 个包子。
每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买 X 个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有 X 个包子。
比如一共有 3 种蒸笼,分别能放 3、4和 5 个包子。
当顾客想买 11 个包子时,大叔就会选 2 笼 3 个的再加 1 笼 5 个的(也可能选出 1 笼 3 个的再加 2 笼 4 个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。
比如一共有 3 种蒸笼,分别能放 4、5和 6 个包子。
而顾客想买 7 个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入格式
第一行包含一个整数 N。
接下来 N 行,每行包含一个整数 Ai。
输出格式
输出一个整数代表答案。
如果凑不出的数目有无限多个,输出INF。
数据范围
1≤N≤100
1≤Ai≤100
输入样例1:
2
4
5
输出样例1:
6
输入样例2:
2
4
6
输出样例2:
INF
样例解释
对于样例1,凑不出的数目包括:1, 2, 3, 6, 7, 11。
对于样例2,所有奇数都凑不出来,所以有无限多个。
思路:
布尔数组f[N][N],f[i][j]表示前用i个数能不能凑出j这个数
对于这些包子:
他们的最大公约数不是1的话则有正无穷个无法凑出的数
分析:
两个数的话,如果这两个数的最大公约数是1,他们不能凑出来的
最大的数是:(a-1)(b-1)-1
如果是n个数的话:
设想:如果用两个数,不能凑出来的最大的数就是 (a-1)(b-1)-1
如果是n个数,那肯定最大的不能凑出来的数肯定比 (a-1)(b-1)-1小
所以枚举装台的时候我们就用这个最大的数来枚举一遍(反正a和b最大为100)
我们就枚举到10000
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=10001;
int a[N];
bool f[N][N];//表示前用i个数能不能凑出j这个数
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;//<pression1>?<pression2>?<pression3>
//1为真则执行2,1为假则执行3
}
int main()
{
int n;
cin>>n;
int d;
//他们的最大公约数不是1的话则有正无穷个无法凑出的数
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
d=gcd(d,a[i]);//求这些数的最大公约数
}
//两个数的话,如果这两个数的最大公约数是1,他们不能凑出来的
//最大的数是:(a-1)(b-1)-1
//如果是n个数的话:
//设想:如果用两个数,不能凑出来的最大的数就是 (a-1)(b-1)-1
//如果是n个数,那肯定最大的不能凑出来的数肯定比 (a-1)(b-1)-1小
//所以枚举装台的时候我们就用这个最大的数来枚举一遍(反正a和b最大为100)
//我们就枚举到10000
f[0][0]=1;
if(d!=1)
{
cout<<"INF"<<endl;
}
else
{
for(int i=1;i<=n;i++)
for(int j=0;j<N;j++)
{
f[i][j]=f[i-1][j];
if(a[i]<=j)f[i][j]|=f[i][j-a[i]];
}
int res=0;
for(int i=0;i<N;i++)
{
if(!f[n][i])res++;
}
cout<<res<<endl;
}
return 0;
}
5、砝码问题(第十二届蓝桥杯省赛第一场C++ A组/B组/研究生组)
你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1,W2,⋅⋅⋅,WN。
请你计算一共可以称出多少种不同的正整数重量?
注意砝码可以放在天平两边。
输入格式
输入的第一行包含一个整数 N。
第二行包含 N 个整数:W1,W2,W3,⋅⋅⋅,WN。
输出格式
输出一个整数代表答案。
数据范围
对于 50 的评测用例,1≤N≤15。
对于所有评测用例,1≤N≤100,N个砝码总重不超过 1e5。
输入样例:
3
1 4 6
输出样例:
10
样例解释
能称出的 10 种重量是:1、2、3、4、5、6、7、9、10、11
1 = 1;
2 = 6 − 4 (天平一边放 6,另一边放 4);
3 = 4 − 1;
4 = 4;
5 = 6 − 1;
6 = 6;
7 = 1 + 6;
9 = 4 + 6 − 1;
10 = 4 + 6;
11 = 1 + 4 + 6。
思路:
01背包问题
bool f[N][M]来表示能不能凑出来
f[i][j]表示从前i中选,能不能凑出M这个重量
状态转移方程:
f[i][j]=f[i-1][j]||f[i-1][j+w[i]]||f[i-1][abs(j-w[i])];
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 2e5 + 10;
int sum;
int n;
int w[N];
bool f[N][M];
int main() {
cin>>n;
for (int i = 1; i <= n; i++)
{
scanf("%d", &w[i]);
sum+=w[i];
}
f[0][0]=true;
for (int i = 1; i <= n;i++)
for (int j = 0; j <=sum;j++)
f[i][j]=f[i-1][j]||f[i-1][j+w[i]]||f[i-1][abs(j-w[i])];
//只要有一个非空,f[i][j]就非空
int ans = 0;
for (int i = 1; i <=sum;i++)
if(f[n][i])ans++;//不为零说明可以选出这个质量的砝码
cout << ans;
return 0;
}
6、潜水员(《信息学奥赛一本通》)
潜水员为了潜水要使用特殊的装备。
他有一个带2种气体的气缸:一个为氧气,一个为氮气。
让潜水员下潜的深度需要各种数量的氧和氮。
潜水员有一定数量的气缸。
每个气缸都有重量和气体容量。
潜水员为了完成他的工作需要特定数量的氧和氮。
他完成工作所需气缸的总重的最低限度的是多少?
例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
输入格式
第一行有2个整数 m,n。它们表示氧,氮各自需要的量。
第二行为整数 k 表示气缸的个数。
此后的 k 行,每行包括ai,bi,ci,3个整数。这些各自是:第 i 个气缸里的氧和氮的容量及气缸重量。
输出格式
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
数据范围
1≤m≤21
1≤n≤79
1≤k≤1000
1≤ai≤21
1≤bi≤79
1≤ci≤800
输入样例:
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
输出样例:
249
思路:
01背包问题,朴素三维也能过
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1003;
int n,m,K;
struct node
{
int o,n,w;
}tool[N];
int f[81][81][N];
int main()
{
cin>>m>>n;//所需要的氧和氮
cin>>K;//气缸的个数
for(int i=1;i<=K;i++)
{
scanf("%d%d%d",&tool[i].o,&tool[i].n,&tool[i].w);//读入该气缸的氧气、氮气、重量
}
memset(f,0x3f,sizeof f);//求最小值要把所有值初始化为无穷大
f[0][0][0]=0;
//f[0][0][1]=0;
int res=1<<31-1;
for(int k=1;k<=K;k++)//枚举气缸 for(int k=1;k<=K;k++)//枚举气缸
for(int i=0;i<=m;i++)//枚举氧气
for(int j=0;j<=n;j++)//枚举氮气
{
f[i][j][k]=f[i][j][k-1];
//f[i][j][k]=f[i][j][k-1];//k-1个尚能满足i和j,k个更能满足i和j了
//if(i>=tool[i].o)f[i][j][k]=min(f[i][j][k],f[i-tool[i].o][j][k]+node[i].w);
//if(i>=tool[i].n)f[i][j][k]=min(f[i][j][k],f[i][j-tool[i].n][k]+node[i].w);
f[i][j][k]=min(f[i][j][k],f[max(0,i-tool[k].o)][max(0,j-tool[k].n)][k-1]+tool[k].w);
}
//cout<<f[m][n][K];
for(int i=1;i<=K;i++)res=min(f[m][n][i],res);
cout<<res;
return 0;
}
/*
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
249
*/
7、糖果(《信息学奥赛一本通》)
由于在维护世界和平的事务中做出巨大贡献,Dzx被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。
在这一天,Dzx可以从糖果公司的 N 件产品中任意选择若干件带回家享用。
糖果公司的 N 件产品每件都包含数量不同的糖果。
Dzx希望他选择的产品包含的糖果总数是 K 的整数倍,这样他才能平均地将糖果分给帮助他维护世界和平的伙伴们。
当然,在满足这一条件的基础上,糖果总数越多越好。
Dzx最多能带走多少糖果呢?
注意:Dzx只能将糖果公司的产品整件带走。
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行 1 个整数,表示糖果公司该件产品中包含的糖果数目,不超过 1000000。
输出格式
符合要求的最多能达到的糖果总数,如果不能达到 K 的倍数这一要求,输出 0。
数据范围
1≤N≤100,
1≤K≤100
输入样例:
5 7
1
2
3
4
5
输出样例:
14
样例解释
Dzx的选择是2+3+4+5=14,这样糖果总数是7的倍数,并且是总数最多的选择。
思路:
f[i][j]表示选择i种类,且j=总价值%MOD的集合中的最大值
((k-a[i])%MOD+MOD)%MOD能保证取模之后的数不是负数
状态转移:
f[i][k]=f[i-1][k];
f[i][k]=max(f[i][k],f[i-1][((k-a[i])%MOD+MOD)%MOD]+a[i]);
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=103;
int f[N][N];//选择i种类,且j=总价值%MOD的集合中的最大值
int a[N];
int n;
int MOD;
int main()
{
scanf("%d%d",&n,&MOD);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
memset(f,-0x3f,sizeof f);
f[0][0]=0;//0没有倍数,所以初始状态为0
for(int i=1;i<=n;i++)
{
for(int k=0;k<=MOD-1;k++)
{
f[i][k]=f[i-1][k];
f[i][k]=max(f[i][k],f[i-1][((k-a[i])%MOD+MOD)%MOD]+a[i]);
}
}
cout<<f[n][0]<<endl;
//cout<<f[n][4]<<endl;
return 0;
}