砝码称重
题目
给定几种重量的砝码,数量不限,判断是否可以称出任意重量的物品。有时间空间限制,时间是1秒内,也就是不能用笨重的穷举法。输入规则,第一行输入一个整数,表示有几个砝码;然后挨个输入每个砝码的重量。可以称重的物品都是整数。能,就输出YES;不能,输出NO。
示例1
1
1
YES
第一个1是有一种重量的砝码,第二个1是这个砝码是多重。这样肯定可以称出任意重量的物品。
示例2
2
2 3
YES
第一个2是有两种重量的砝码,第二行2 3表示每种砝码重量是2和3,那么也是可以称出任意重量的物品。
示例3
2
2 4
NO
这一组只能得到2N物品的重量,无法称出奇数物品的重量。
思考
什么条件可以称出任意重量的物品呢?考虑到素数的原因,那么只有得到重量为1的砝码,才可以实现目标。
第一个思路,给定的砝码相互相加,直到有两个重量相差为1。但是这里有一个边界问题,什么时候可以结束呢?相加可以无上限,放弃。
第二种思路,让砝码相减,直到算出1。这种方法貌似是可行的,并且可以很好的判断边界,就是每次相减都会产生一个小于最大砝码值,大于等于0,并且不在已经得出的砝码重量里面的新数值。如果一次循环没有得到新数值,也就是每次相减得到的数都已经存在了,那么结束。拿上面的示例举例,示例1,因为直接得到1,可以;示例2,3-2=1,可以;示例3,4-2=2 2-2=0,只能得到0 2 4,没有1,所以不可以。代码如下
bool proone(map<int, bool>& fmweight) { if (fmweight[1]) { return false; } vector<int> tmppronum; for (auto iter = fmweight.rbegin(); iter != fmweight.rend(); iter++) { if (!iter->second) { continue; } auto iter1 = iter; iter1++; for (; iter1 != fmweight.rend(); iter1++) { if (!iter1->second) { continue; } int tmpnum = iter->first - iter1->first; if (fmweight.count(tmpnum) == 0 || !fmweight[tmpnum]) { tmppronum.emplace_back(tmpnum); } } } if (!tmppronum.empty()) { for (auto& iter : tmppronum) { fmweight[iter] = true; } return true; } return false; } bool test() { int fmnum = 0; cin >> fmnum; map<int, bool> fmweight; fmweight[1] = false; for (size_t i = 0; i < fmnum; i++) { int tmp; cin >> tmp; fmweight[tmp] = true; } while (proone(fmweight)) { } if (fmweight[1]) { return true; } return false; }
这种方法试了,数量相近是可以的,但是如果跨度太大,时间上就不行了。示例,输入3,然后输入9 8000 20000,计算的非常慢。
怎么优化呢?思路应该变一下,引入丢番图方程,参考(https://blog.csdn.net/luyuncheng/article/details/8558162),并且感谢群里的小伙伴。
定理2:如果a1,a2,…,an是正整数,那么方程a1*x1+a2*x2+…+an*xn=c有整数解,当且仅当d=(a1,a2,……,an)能整除c,另外当存在一个解的时候,那么方程有无穷多个解
a1 a2表示砝码质量,x1 x2表示这种砝码需要几个,把c设置为1,就是我们需要解决的问题,n元一次方程是否有解。也就是求给定的n中砝码重量的最大公约数是1.
bool test() { int fmnum = 0; cin >> fmnum; set<int> fmweight; int imax = 0; int imin = 0; for (size_t i = 0; i < fmnum; i++) { int tmp; cin >> tmp; imax = tmp; imin = tmp; fmweight.insert(tmp); } if (fmnum == 0) { return false; } if (fmnum == 1 && imax != 1) { return false; } for (auto& iter : fmweight) { imax = iter; if (imin > imax) { imin = imin ^ imax; imax = imin ^ imax; imin = imin ^ imax; } while (0 != imax % imin) { imax = imax / imin; if (imin > imax) { imin = imin ^ imax; imax = imin ^ imax; imin = imin ^ imax; } } if (imin == 1) { return true; } } return false; }
群里的小伙伴说反复求余也可以。具体没有证明。就是不断的把给出的砝码重量求余,然后再把余数加进来计算,知道求出1或是求不出1.