[USACO 6.3.1] Fence Rails
题目大意
给N个背包,R件物品,把R件物品放进N个背包中,求最多能放多少.
题解
搜索出全部的方案再找当然是行不通的,因为这是一高纬度的背包问题.
我们可以先枚举答案然后判断可行性.这样就会快很多了.
但是即使如此,搜索的速度依然会比较慢.
我们可以采取以下优化.
1.搜索顺序: 先放大的背包;
2.去重: 把背包进行编号,物品也进行编号,相同的物品(体积相同)有不同的编号,保证编号大的物品所在的背包编号一定大于或等于编号小的物品所在的背包编号.
3.可行性剪枝: 如果不能放任何物品的背包的剩余容量之和大于应剩余的容量则视为不可行方案.这样说大家可能不理解,我举个例子.
假如要放M个物品,现在已经在前i个背包中放了j个物品,并且这i个背包已经容不下剩下M-j个物品中的任何一个,那么我们知道这i个背包中还是有剩余容量的,但这些剩余容量基本是废了的.在最好情况下,是放完了M个物品没有出现任何废容量.我们可以理解一下,背包容量之和减去要放M个物品的体积代表的是放这M个物品最多能允许多少"废容量"的出现.当"废容量"大于这个极限即视为不可行方案.
剪枝3是比较强力的剪枝.加了这三个剪枝后,AC是没有什么问题的了.
还有,我们可以使用二分法来代替枚举,这样减少了DFS的次数,增强了算法稳定性.
代码
/* TASK:fence8 LANG:C++ */ #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int n, r, wood[55], need[1024], sum[1024], ans, space, tot; int maxw[55]; bool dfs(int dep, int pos) { if (dep == 0) return true; if (space > tot - sum[ans]) return false; for (int i = pos; i <= n; ++i) if (wood[i] >= need[dep]) { wood[i] -= need[dep]; if (wood[i] < need[1]) space += wood[i]; bool flag = false; if (need[dep] == need[dep - 1]) flag = dfs(dep - 1, i); else flag = dfs(dep - 1, 1); if (wood[i] < need[1]) space -= wood[i]; wood[i] += need[dep]; if (flag) return true; } return false; } int bs(int left, int right) { while (left < right) { ans = (left + right) / 2 + 1; space = 0; if (dfs(ans, 1)) left = ans; else right = ans - 1; } return left; } bool cmp(int a, int b) { return a > b; } int main() { freopen("fence8.in", "r", stdin); freopen("fence8.out", "w", stdout); scanf("%d", &n); tot = 0; for (int i = 1; i <= n; ++i) scanf("%d", &wood[i]), tot += wood[i]; sort(wood + 1, wood + n + 1, cmp); scanf("%d", &r); sum[0] = 0; for (int i = 1; i <= r; ++i) scanf("%d", &need[i]); sort(need + 1, need + r + 1); for (int i = 1; i <= r; ++i) sum[i] = sum[i - 1] + need[i]; printf("%d\n", bs(0, r)); return 0; }