第八届蓝桥杯第八题–包子凑数(C语言)
一.比赛题目
1.题目要求
小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有X个包子。比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
2.输入与输出
输入:
第一行包含一个整数N。(1 <= N <= 100)以下N行每行包含一个整数Ai。(1 <= Ai <= 100)
输出:
一个整数代表答案。如果凑不出的数目有无限多个,输出INF
样例输入1:
2
4
5
样例输出1:
6
样例输入2:
2
4
6
样例输出2:
INF
二.分析过程
为了做懂这道题,这两天一直在看背包问题,终于在今晚上把背包问题稍微总结了一下:总结的背包问题
1.问题分析
这个包子凑数问题首先要解决两个难点:
(1)在什么情况下,包子凑不出来的数目是无限个;
(2)如何知道包子能凑出哪些数目;
第二个问题其实就是背包问题的一种变形
2.第一个问题
第一个问题的关键其实就是知道假如输入的每笼包子的数目互质的话,包子凑不出来的数目就是无限个;
一直反复来求两个数的公约数,假如最后不是1,那么就说这两个数不互质,求出两个数的最大公约数之后在和后面的数又进行比较,
gcd函数返回两个或多个整数的最大公约数
这就是下面代码的含义:
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a%b);
}
3.第二个问题
包子凑数里面说了笼数是无穷笼,所以可以利用完全背包里面的转移方程来解决:
for(int i=1;i<=n;i++)
for(int j=0;j<=h;j++)
if(j>=w[i])
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
//完全背包的转移方程
在结合本题的实际情况下,对转移方程代码进行改动:
for(int i=1;i<=n;i++)
for(int j=w[i];j<N;j++)
dp[j]=max(dp[j],dp[j-w[i]]);
//w[i]就是第i笼的包子数目;
且在上面代码的情况下,我们要将dp[0]赋值为1才行;
dp[0] = 1;
因为永远也凑不到比每笼包子数还小的数,所以j可以从w[i]开始;
4.整体分析
我们在这里其实是把数目设定为1—100000(因为设定的数目大,就假定他不会超出上限);#define N 100001
同时把dp数组来作为能组成的数目集合,dp[i]!=0来作为不能被凑到的包子数,dp[i]==1来表示能凑到的包子数;
是为什么能这样呢?
用背包问题中最易懂的填表来变达前面的转移方程:
以样例输入1为例:
w[i] | 包子数/1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | … |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
4 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | … |
5 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | … |
就和背包问题一样了
用0来表示凑不到的数目,用1来表示能凑到的数目;
三.整体代码
#include <stdio.h>
#include <string.h>
#define N 100001
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a%b);
}
//返回多个数的最大公约数;
int max(int a,int b)
{
return a>=b?a:b;
}
//比较大小;
int main()
{
int w[N];
int n;
int dp[N];
memset(dp,0,sizeof(dp));
//把dp数组赋值为0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&w[i]);
}
//输入每笼包子数目;
int g = w[1];
for(int i = 2 ; i <= n ; i ++)
g = gcd(g,w[i]);
if(g != 1)
{
printf("INF\n");
}
//输出INF的情况;
else
{
dp[0] = 1;
for(int i=1;i<=n;i++)
for(int j=w[i];j<N;j++)
dp[j]=max(dp[j],dp[j-w[i]]);
//背包问题转移方程变形;
int cnt = 0;
for (int i = 1; i < N; i++)
if (dp[i]==0)
cnt++;
printf("%d",cnt);
}
return 0;
}
四.总结
这道题就是背包问题和gcd函数的综合,所以算法确实也挺重要的,但是看了一天多才总结点点背包问题,结果动态规划里面还有好多东西要看啊,真让人头秃。