考慮構成:\(k<=16\)因此根據\(k\)構造狀壓\(dp\),將全部硬幣的使用狀況進行狀態壓縮git
考慮狀態:數組\(dp[i]\)表示用\(i\)狀態下的硬幣能夠購買到第幾個商品 ,\(f[i]\)表示狀態\(i\)下的花費數組
考慮轉移:使用當前硬幣的狀態必定由使用上一個硬幣的狀態轉移而來優化
舉個例子:以前狀態\(x\):\(dp[x] = y\), \(i = 2 = (010)_2\) ,當前枚舉到的狀態\(i = 3 = (011)_2\) , \(dp[i] = (dp[x] + 1\)開始能買到哪裏\((<=n))\), 至關於狀態\(x\)能購買到\(y\)號物品,\(i\)要從\(y+1\)號開始購買 ,\(i\)狀態比\(x\)狀態在二進制的第三位多了1,說明比i狀態多用了一個編號爲1的硬幣,\(f[i] = f[x]\) + 硬幣\(1\)的價值
狀態轉移完成spa
考慮具體作法:外層循環枚舉全部狀態,內層循環枚舉每一位,若當前狀態\(i\)的第\(j\)位爲\(1\),則能夠進行轉移code
而後能夠進行枚舉\(n\)件物品,考慮每一件是否能夠購買,一直到不能購買爲止get
由於一種狀態能夠被更新屢次,因此要取\(max\),保證\(dp\)數組存的是能買到的最大編號,而後更新\(dp\)數組和\(f\)數組it
若是到第\(n\)件均可以買,則能夠購買所有物品,\(ans\)記錄當前的最小花費,最後用全部硬幣的總面值減去最小花費即爲答案io
若是\(ans\)沒有被更新過,說明不能購買,輸出\(-1\)class
時間複雜度\(O(2^kkn)\),超時循環
考慮優化:發現每次枚舉物品統計價值來檢查是否可以購買是冗餘操做,能夠用前綴和預處理一下,而後每次檢查的時候進行一次二分就能夠了
時間複雜度\(O(2^kklogn)\),可經過本題
考慮正確性:由於外層循環枚舉狀態是從小到大枚舉,因此保證當前狀態某一位少\(1\)(即當前使用硬幣數減一)的狀態已經被轉移過了
注意事項:
1.不要錯誤理解題意,注意每次支付只能支付一枚硬幣 ,不能算把硬幣湊出來的總錢數而後判斷能購買多少,這種錯誤作法能拿到\(93\)分的好成績(大霧)是由於數據太水
2.二分的時候注意初始的左端點,由於從使用當前硬幣的狀態轉移過來,因此要從使用當前硬幣前狀態所能購買到的物品\(+1\)做爲左端點進行二分,右端點不會變化一直是\(n\)
3.由於二分時要檢查的值要與前綴和數組進行比較,因此比較時前綴和數組應該減去左端點以前的前綴
4.注意二分的邊界問題以及最後的返回值
代碼:
#include <cstdio> #include <cctype> #define min(a, b) a < b ? a : b #define MAXN 100001 #define N 17 int n, m, tot_money, ans = 2147483647; int dp[1 << N], f[1 << N], sum[MAXN], pay[MAXN], coin[MAXN]; inline int read() { int s = 1, w = 0; char ch = getchar(); for(; ! isdigit(ch); ch = getchar()) if(ch == '-') s = -1; for(; isdigit(ch); ch = getchar()) w = w * 10 + ch - '0'; return s * w; } inline int check(int x, int cha) { int l = cha, r = n, mid; while(l <= r) { mid = (l + r) >> 1; if(sum[mid] - sum[cha - 1] == x) return mid; if(sum[mid] - sum[cha - 1] < x) l = mid + 1; else r = mid - 1; } return r; } int main() { m = read(), n = read(); for(int i = 1; i <= m; i ++) coin[i] = read(), tot_money += coin[i]; for(int i = 1; i <= n; i ++) pay[i] = read(), sum[i] = sum[i - 1] + pay[i]; for(int i = 1; i < (1 << m); i ++) { for(int j = 0; j < m; j ++) if(i & (1 << j)) { int x = (i ^ (1 << j)), sum; if((sum = check(coin[j + 1], dp[x] + 1)) > dp[i]) dp[i] = sum, f[i] = f[x] + coin[j + 1]; if(dp[i] == n) ans = min(f[i], ans); } } printf("%d", (tot_money - ans) < 0 ? -1 : tot_money - ans); return 0; }