AcWing 281. 硬币
考察:多重背包dp
实际上是考察多重部分和的问题.
在本题如果直接用二进制优化时间复杂度是108 会TLE.因此必须优化
多重背包问题中f[j] = 1是因为上一轮f[j] = 1或者本轮f[j-v] = 1.如果f[j]在枚举第i-1种硬币时就已经为1,那么f[j]在第i轮就没必要在赋值.
这里有两种不同的思路但本质是相同的:
lyd大佬的思路:
需要用一个数组记录f[j]在阶段i时至少需要几枚硬币.会有几种情况
- f[j]在第i-1轮已经为1,那么不需要赋值直接跳过
- f[j-v]在第i轮为1.那么我们需要判断此时再拿一个v会不会超出给定数目,如果超出就跳过.
因为used数组是用来记录阶段i,f[j] = 1要几枚硬币,used数组也起到约束范围的作用,同时避免了三层循环.(我觉得使用used数组就行了...)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 typedef long long ll; 7 const int N = 110,M = 100010; 8 int a[N],c[N]; 9 int f[M],used[M]; 10 int main() 11 { 12 int n,m; 13 while(scanf("%d%d",&n,&m)&&(n+m)) 14 { 15 memset(f,0,sizeof f); int ans = 0; 16 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 17 for(int i=1;i<=n;i++) scanf("%d",&c[i]); 18 f[0] = 1; 19 for(int i=1;i<=n;i++) 20 { 21 memset(used,0,sizeof used); 22 for(int j=a[i];j<=m;j++) 23 if(!f[j]&&f[j-a[i]]&&used[j-a[i]]+1<=c[i]) f[j] = 1,used[j] = used[j-a[i]]+1; 24 } 25 26 for(int i=1;i<=m;i++) if(f[i]) ans++; 27 printf("%d\n",ans); 28 } 29 return 0; 30 }
白书的思路:
其实别无二致.区别就是白书的f[i][j]数组是记录第i种硬币下,凑成f[j]还剩下多少货币.(但是实测POJ 2000MS,lyd的方法1297ms)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 typedef long long ll; 7 const int N = 110,M = 100010; 8 int a[N],c[N],v[N*10]; 9 int f[M]; 10 int main() 11 { 12 int n,m; 13 while(scanf("%d%d",&n,&m)&&(n+m)) 14 { 15 memset(f,-1,sizeof f); int ans = 0; 16 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 17 for(int i=1;i<=n;i++) scanf("%d",&c[i]); 18 f[0] = 0; 19 for(int i=1;i<=n;i++) 20 for(int j=0;j<=m;j++) 21 { 22 if(f[j]>=0) f[j] = c[i]; 23 else{ 24 if(j<a[i]||f[j-a[i]]<=0) f[j] = -1; 25 else if(j>=a[i]&&f[j-a[i]]>0) f[j] = f[j-a[i]]-1; 26 } 27 } 28 for(int i=1;i<=m;i++) if(f[i]>=0) ans++; 29 printf("%d\n",ans); 30 } 31 return 0; 32 }
关于白书代码又有几个问题:
1.二层循环能不能从a[i]开始.
答案是不能,二层循环是有两个分支点:
- 如果f[j]>=0.这代表上一轮就已经凑成j.
- f[j]<0此时我们需要从前面f[j-v]判断f[j-v]剩余的货币数还能不能给f[j].
如果从a[i]开始,我们的f[i][0]是没有初始化为c[i]的.那么就不能得到货币.如果每次循环给f[i][0]赋值,那和直接放在初始条件有什么区别
判别答案只需要判断当前凑成j钱的时候剩余钱数是不是>=0(=0说明刚好用完)
注意:
这道题和二进制优化是不一样的,二进制优化最后变成了01背包,每个只能拿一次,所以f[i][j]如果去掉第i种只能从f[i-1][j-v]推导
而这两种优化思路是返回了原本三重循环的多重背包.所以从f[i][j-v]推导