题意分析
给你n种不同价值的硬币,价值为val[1],val[2]...val[n],每种价值的硬币有num[1],num[2]...num[n]个,问使用这n种硬币可以凑齐[1,m]内多少价值(换句话说,就是可以恰好支付的价格有多少)
解题思路
一开始觉得这个题也不是很难,就是多重背包问题,但是用二进制优化的多重背包写法TLE后,陷入了深思...
看了数据范围,二进制优化的时间复杂度为O(∑ log(num[i] * V),加上多组输入后....应该是没被冤枉了.....
二进制都不行了,怎么办呢?只能去用单调队列优化多重背包问题了,其时间复杂度为O(n*V),时间刚刚好卡过去...
但是这里又出了一个问题,我用单调队列优化求解仍然得到了TLE?
QAQ,难道还有比单调队列更优的解法?借鉴大佬的题解后,发现这个题目严格意义上算是一个混合背包问题,而用单调队列处理完全背包问题的时候,效率很低,所以将这个题目按照混合背包分别处理后,就可以AC了。
(单调队列优化的讲解可以看看这个,讲的很清晰:http://www.cppblog.com/flyinghearts/archive/2010/09/01/125555.html)
代码区
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x, y) cout<<"["<<x<<","<<y<<"] " #define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 1e9 + 7; const ll mod = 1e9 + 7; const int Max = 1e5 + 10; int n, v; int que[Max], vol[Max], num[Max]; int head, tail; bool dp[Max]; int main() { #ifdef LOCAL // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); #endif while (scanf("%d%d", &n, &v) != EOF && n + v) { for (int i = 1; i <= n; i++) scanf("%d", vol + i); //将价值当作体积,无需计算最大价值 for (int j = 1; j <= n; j++) scanf("%d", num + j); memset(dp, false, sizeof(dp)); dp[0] = true; int cnt = 0; for (int i = 1; i <= n; i++) //题目虽然没明说这个是混合背包,但是单调队列处理01背包和完全背包用时较长, //故将之用混合背包处理 { if (num[i] == 1) //01背包 { for (int j = v; j >= vol[i]; j--) if (dp[j - vol[i]] && !dp[j]) dp[j] = true, cnt++; continue; } if (vol[i] * num[i] >= v) //完全背包,这个比较关键,因为用单调队列处理非常耗时 { for (int j = vol[i]; j <= v; j++) if (dp[j - vol[i]] && !dp[j]) dp[j] = true, cnt++; continue; } for (int res = 0; res < vol[i]; res++) //单调队列优化处理 { head = 0, tail = -1; int sum = 0; for (int k = res; k <= v; k += vol[i]) { if (tail - head == num[i]) sum -= que[head++]; que[++tail] = dp[k]; sum += dp[k]; if (sum && !dp[k]) dp[k] = true, cnt++; } } } printf("%d ", cnt); } return 0; }