[POJ1742] Coins [多重揹包]
阿新 • • 發佈:2018-11-12
三種方法。
寫得比較優秀的二進位制拆分+分資料範圍處理、多重揹包可行性、單調佇列優化。
第一種方法。
一般的二進位制拆分複雜度是
。算一下就能發現這個上界卡到
。
考慮怎麼降低處理數量多的硬幣的複雜度。顯然假如
就可以當作完全揹包來跑。
那就這麼做。
注意多重揹包的二進位制拆分是允許重複的,
如果是單純把 二進位制表示裡為 的位拿出來並不能組合得到所有不大於 的方案
一定要從 開始倍增,最後把減剩下的 單獨拿出來再跑一次。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
using namespace std;
int n, m, c;
int a[105];
bool f[100005];
int main() {
while (~scanf("%d%d", &n, &m)) {
if (!n && !m) return 0;
for (int i = 1; i <= m; ++i) {
f[i] = 0;
}
f[0] = 1;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &c);
if (c * a[i] < m) {
for (int t, k = 1; k <= c; k <<= 1) {
t = k * a[i];
for (int j = m; j >= t; --j) {
f[j] |= f[j - t];
}
c -= k;
if (!c) break;
}
if (c) {
int t = c * a[i];
for (int j = m; j >= t; --j) {
f[j] |= f[j - t];
}
}
}
else {
for (int j = a[i]; j <= m; ++j) {
f[j] |= f[j - a[i]];
}
}
}
int ans = 0;
for (int i = 1; i <= m; ++i) ans += f[i];
printf("%d\n", ans);
}
return 0;
}
第二種方法。
實際上這種方法是很自然的。
我的程式碼比較醜,漂亮一點的程式碼應該看一眼就可以明白原理了。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
using namespace std;
int n, m, c, ans;
int a[105];
int vis[100005];
bool f[100005];
void Check(const int &x) {
for (int i = 1; i <= m; ++i) {
vis[i] = 0;
}
f[0] = 1;
for (int i = a[x]; i <= m; ++i) {
if (vis[i - a[x]] == c) continue;
if (f[i] || !f[i - a[x]]) continue;
f[i] = 1;
vis[i] = vis[i - a[x]] + 1;
++ans;
}
}
int main() {
while (~scanf("%d%d", &n, &m)) {
if (!n && !m) return 0;
ans = 0;
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &c);
Check(i);
}
printf("%d\n", ans);
}
return 0;
}
第三種方法。
實際上這道題目完全沒必要用單隊解、不過也是可以的。
多重揹包樸素式子
外層列舉物品,把
重標號,按照
的餘數分類,轉化式子。可以得到
(列舉
)
這是單調佇列優化的普遍形式,在單調佇列中維護
每次將
入隊,得到的最大值是
到這道題目裡面有