[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;
}

  

posted @ 2015-03-07 07:55  JoeFan  阅读(498)  评论(0编辑  收藏  举报