[Acwing蓝桥杯DP] 1226. 包子凑数
题目大意:有n层不同的包子,这n层包子的各不形同,每层的数量是无限的 要用这些层包子凑出不同数量的包子数 如果不能凑出的包子数是无数个,就输出INF,如果不是无数个,就输出有限的不能凑出的包子的个数。
数据范围:1<=n<=100
要凑出的总包子数是无数个
分析:每层的包子是可以无限使用的
这就联想到了完全背包问题,每个背包可以无限的用
关键是总包子数是无限的 这个条件限制了M是无穷大的
总数到底是多少呢?
这里就要用到数论的知识:
裴蜀定理:任意两个数的组合必定是他们gcd的任意两个数的组合必定是他们gcd的倍数,同样可以推广到更多数:如果这些数的gcd是d,那么他们的组合是d的倍数,如果dd不是1,那么必然有无限个数无法被组合出来。
那么最大的不能表达的数一定要有一个上界M啊
要不状态转移方程怎么定义范围?
当gcd=1,两个数a,b,最大不能表示出来的 数是:(a−1)(b−1)−1。当数字更多的时候,这个上界必然更小(可选的数字变多了),而99和98是100内最大的互质的数,所以这个上界选择10000。(暂且无法证明)
那么就用闫氏DP分析法:
集合f [ i ] [ j ] 表示:在前i个数中选任意多个,是否能到达 总数量为 j ? ( true : false )
属性:是否存在
区域划分
由最后不同的一步来划分集合,在重量为j的时候,第i 个物品选了几件。
可以分为:dp(i,j)=dp(i−1,j) || dp(i−1,j−w(i)) || … || dp(i−1,j−k∗w(i)) (k=j/w(i))
但是,完全背包问题有个特殊的地方:那就是 状态重叠 :
所以最终方程为:f [ i , j ]=f [ i−1, j ] || f [ i , j−w(i) ] (w(i)≤j)
初始化:f [ 0 ][ 0 ] = 1; //什么也不选的时候重量为0
那么代码如下:
二维空间的写法(朴素写法)
#include <bits/stdc++.h> using namespace std; const int N=110,M=10010; bool f[N][M];//f[i][j]表示:考虑前i个物品总重量为j的方案是否存在 int n; int w[N]; int gcd(int a,int b) { return b?gcd(b,a%b):a; } int main() { cin>>n; int d=0; for(int i=1;i<=n;i++) { scanf("%d",&w[i]); d=gcd(d,w[i]); } if(d!=1)puts("INF"); else { f[0][0]=1; for(int i=1;i<=n;i++) { for(int j=0;j<M;j++) { f[i][j]=f[i-1][j]; if(j>=w[i])f[i][j]|=f[i][j-w[i]]; } } int res=0; for(int i=0;i<M;i++) { if(!f[n][i])res++; } cout<<res<<endl; } return 0; }
这样的话空间比较大 毕竟f [ 110] [10010]
优化一下空间:
优化完空间后的写法
#include <bits/stdc++.h> using namespace std; const int N=110,M=10010; bool f[M];//f[i][j]表示:考虑前i个物品总重量为j的方案是否存在 int n; int w[N]; int gcd(int a,int b) { return b?gcd(b,a%b):a; } int main() { cin>>n; int d=0; for(int i=1;i<=n;i++) { scanf("%d",&w[i]); d=gcd(d,w[i]); } if(d!=1)puts("INF"); else { f[0]=1; for(int i=1;i<=n;i++) { for(int j=w[i];j<M;j++) { f[j]|=f[j-w[i]]; } } int res=0; for(int i=0;i<M;i++) { if(!f[i])res++; } cout<<res<<endl; } return 0; }
END!!!

浙公网安备 33010602011771号