不知道该叫什么,那就砝码称重吧
题目:砝码称重(洛谷P2347)
题目描述
设有1g1g、2g2g、3g3g、5g5g、10g10g、20g20g的砝码各若干枚(其总重\le 1000≤1000),
输入格式
输入方式:a_1 , a_2 ,a_3 , a_4 , a_5 ,a_6a1,a2,a3,a4,a5,a6
(表示1g1g砝码有a_1a1个,2g2g砝码有a_2a2个,…,20g20g砝码有a_6a6个)
输出格式
输出方式:Total=NTotal=N
(NN表示用这些砝码能称出的不同重量的个数,但不包括一个砝码也不用的情况)
TLE的代码:
#include<cstdio> #include<algorithm> using namespace std; int main() { int f[1005] = {}, c[7] = {0,1,2,3,5,10,20}, sav[1003] = {}, cnt = 0, ans = 0; for(int i = 1;i <= 6;i++) { scanf("%d", &sav[i]); cnt += c[i] * sav[i]; } for(int i = 1;i <= 6;i++) { for(int j = 0;j <= cnt;j++) { for(int k = 1;k <= sav[i];k++) { int add = c[i] * k; if(j==0) { f[add] = 1; } else { if(f[j] == 1) { f[j+add] = 1; } } } } } for(int i = 1;i <= cnt;i++) { if(f[i] == 1) { ans++; } } printf("Total=%d", ans); return 0; }
正解代码:
#include<cstring> #include<cstdio> using namespace std; int main() { int w[7] = {0,1,2,3,5,10,20}, v[7] = {}, ans = 0, f[1002] = {}; f[0] = 1; for(int i = 1;i <= 6;i++) { scanf("%d", &v[i]); } for(int i = 1;i <= 6;i++) { for(int j = 1;j <= v[i];j++)//次数 (相当于把每一个砝码看作单独的物体) { for(int k = 1000;k >= 0;k--)//防止多次使用 { if(f[k]) f[k + w[i]] = 1; } } } for(int i = 1;i <= 1000;i++) { if(f[i]) ans++; } printf("Total=%d", ans); return 0; }
可以发现:
TLE的代码是先枚举砝码重量,再枚举已得到的砝码的能拼成的重量和,再枚举每次选取的当前种类砝码的个数。
这显然复杂度爆炸(几乎就没有任何优化,基本上用最差的方式枚举每一种组合)
而修改后的正解代码先枚举种类,在再用j次当前种类的砝码(注意不是枚举每次选的个数,而是总共选j次),最后再选已经拼出的砝码重量。
这个的复杂的显然更优。
由此,我们可以总结出几点规律(在统计种类或方案数的问题中):
为了让时间复杂度更优,要尽量减少枚举物品件数这一步产生的计算(即这一步在多层循环中所在的位置要尽量靠外)。
尽量把同种有限件数的物品看作不同的物品枚举。