考虑构成:\(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; }