[BZOJ 1082] [SCOI2005] 栅栏 【二分 + DFS验证(有效剪枝)】
题目链接:BZOJ - 1082
题目分析
二分 + DFS验证。
二分到一个 mid ,验证能否选 mid 个根木棍,显然要选最小的 mid 根。
使用 DFS 验证,因为贪心地想一下,要尽量先用提供的小的木木棍,尽量先做出需要的大的木棍,所以要先将提供的木棍和需要的木棍都排序。
DFS 的时候是按照需要的木棍从大到小的顺序一层一层搜,每一层上是按照从小到大的顺序枚举提供的木棍。(当然枚举的时候已经不一定是从小到大了,有些木棍已经被截掉了一些。)
要使用两个有效的剪枝:
1)如果下一层的木棍和这一层的木棍等长,就从这一层木棍枚举到的提供的木棍开始枚举,因为前面的都是不可以的。
2)当一根木棍被截掉一段之后小于最小的需要的木棍,那么它就废掉了,记录当前废掉的木棍总长Rest,如果Rest + 1到mid所有木棍的总长 > 提供的所有木棍总长,那么就返回 false。
代码
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cstring> #include <cmath> using namespace std; const int MaxM = 50 + 5, MaxN = 1000 + 5; int n, m, Ans, Rest, Need, Tot; int A[MaxM], B[MaxN], Sum[MaxN]; bool DFS(int x, int y) { if (Rest + Need > Tot) return false; bool ret = false; for (int i = y; i <= m; ++i) { if (A[i] >= B[x]) { A[i] -= B[x]; if (A[i] < B[1]) Rest += A[i]; if (x == 1) ret = true; else if (B[x - 1] == B[x]) ret = DFS(x - 1, i); else ret = DFS(x - 1, 1); Rest -= A[i]; A[i] += B[x]; if (ret) return true; } } return false; } int main() { scanf("%d", &m); for (int i = 1; i <= m; ++i) scanf("%d", &A[i]); scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &B[i]); sort(A + 1, A + m + 1); for (int i = 1; i <= m; ++i) Tot += A[i]; sort(B + 1, B + n + 1); for (int i = 1; i <= n; ++i) Sum[i] = Sum[i - 1] + B[i]; int l, r, mid; l = 0; r = n; Ans = 0; while (l <= r) { mid = (l + r) >> 1; Need = Sum[mid]; Rest = 0; if (DFS(mid, 1)) { Ans = mid; l = mid + 1; } else r = mid - 1; } printf("%d\n", Ans); return 0; }