題目
給定幾種重量的砝碼,數量不限,判斷是否可以稱出任意重量的物品。有時間空間限制,時間是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.