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

  

时间: 2024-10-14 19:33:24

[BZOJ 1082] [SCOI2005] 栅栏 【二分 + DFS验证(有效剪枝)】的相关文章

【BZOJ 1082】[SCOI2005]栅栏 二分+dfs

对于最优解我们发现所有的最优解都可以是前多少多少个,那么我们就二分这个前多少多少个,然后用dfs去判解,我们发现在dfs的过程中如果不剪枝几乎必T,所以我们就需要一些有效的剪枝 I. 我们在枚举过程中每个数选什么是有前后顺序的,然而对于一些相同的数他们并没有顺序我们可以记录上个数的选择点,如果两数相同,那么就从上个数的选择点开始那么时间复杂度就从次方级别降到了组合数级别,是飞跃式的 II. 然后我们发现先对于枚举顺序与选择顺序的选择是玄学的,我们可以视为他们都没影响那么我们就可利用这个了,如果我

bzoj1082: [SCOI2005]栅栏(二分答案搜索判断)

1082: [SCOI2005]栅栏 题目:传送门 题解: 是不是一开始在想DP?本蒟蒻也是qwq,结果很nice的错了ORZ 正解:二分+搜索 我们可以先把两种木材都进行排序,那么如果需要的最大木材比可提供的最大木材还要大的话,那么可以直接舍弃这种需要的木材. 然后就可以进入二分,如果当前可以做贡献的提供木材加起来都没有前mid块需要木材大的话,很明显当前mid不ok 返回判断值再记录答案就好了,注意一些小细节的优化 代码: 1 #include<cstdio> 2 #include<

Bzoj 1085: [SCOI2005]骑士精神 (dfs)

Bzoj 1085: [SCOI2005]骑士精神 题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1085 dfs + 剪枝. 剪枝方法: 1.每次交换只能改变一个位置.若发现之间相差的步数加上以前走的步数大于15的话,直接舍弃这一状态. 2.初始时,\(ans\)设为\(16\) 有了上面两个剪枝就A了. 照这节奏,SCOI2005就刷完了??? #include <iostream> #include <cstdio>

【BZOJ】1082: [SCOI2005]栅栏(二分+dfs)

http://www.lydsy.com/JudgeOnline/problem.php?id=1082 最近被这种神题虐cry...这还竟然是usaco的题QAQ我竟然如此弱....(我是不是写过这题?反正好像有点印象的样子..好像又不是..) 一开始写了个背包...贪心的找................然后造了几个数据,,wa了.. QAQ 膜拜题解.orz 首先我们得到的k个木板一定是在n个中最小的k个...(这个太显然了QAQ 我们考虑将m个提供的木材,依次从最小的放(显然先用完最短的

BZOJ 1086 [SCOI2005]王室联邦 ——DFS

手把手教你树分块系列. 只需要记录一个栈,如果等于B的情况就弹栈,令省会为当前节点. 然后把待分块的序列不断上传即可. 考虑到有可能弹出不是自身节点的子树节点,所以记录一下当前的栈底. DFS即可 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define maxn 2005 #define F(i,j,k

1082: [SCOI2005]栅栏

恩,看了他人的题解,才勉强理解,希望能有所收获. 思路:这道题要使用搜索+二分,可能有部分像我一样的会觉得这道题难道不是排序后一个一个去判断就可以了吗?那么这里可以举个例子! 这幅图你就会发现,如果按一个个取,就会发现只能得到2和3,但实际上6会切成2和4,3切成3,三种都会得到. 所以不是能取就切的,2应该在6那里取. 我们用回溯的方法,去看看这块木板应从哪里切,这样只要有一种情况下,能切出来就成立,二分枚举我们切前几块. 好啦,具体思路就从程序里看吧! #include<iostream>

BZOJ1082: [SCOI2005]栅栏

1082: [SCOI2005]栅栏 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1906  Solved: 816[Submit][Status][Discuss] Description 农夫约翰打算建立一个栅栏将他的牧场给围起来,因此他需要一些特定规格的木材.于是农夫约翰到木材店购买木材.可是木材店老板说他这里只剩下少部分大规格的木板了.不过约翰可以购买这些木板,然后切割成他所需要的规格.而且约翰有一把神奇的锯子,用它来锯木板,不会产生任

【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

第一种做法(时间太感人): 这题我真的逗了,调了一下午,疯狂造数据,始终找不到错. 后来发现自己sb了,更新那里没有打id,直接套上u了.我.... 调了一下午啊!一下午的时光啊!本来说好中午A掉去学习第二种做法,噗 好吧,现在第一种做法是hld+seg+bst+二分,常数巨大,log^4级别,目前只会这种. 树剖后仍然用线段树维护dfs序区间,然后在每个区间建一颗平衡树,我用treap,(这题找最大啊,,,囧,并且要注意,这里的rank是比他大的数量,so,我们在二分时判断要判断一个范围,即要

[BZOJ 1085] [SCOI2005] 骑士精神 [ IDA* 搜索 ]

题目链接 : BZOJ 1085 题目分析 : 本题中可能的状态会有 (2^24) * 25 种状态,需要使用优秀的搜索方式和一些优化技巧. 我使用的是 IDA* 搜索,从小到大枚举步数,每次 DFS 验证在当前枚举的步数之内能否到达目标状态. 如果不能到达,就枚举下一个步数,重新搜索,即使某些状态在之前的 DFS 中已经搜索过,我们仍然搜索. 并且在一次 DFS 中,我们不需要判定重复的状态. 在 IDA* 中,重要的剪枝优化是 估价函数 ,将一些不可能存在可行解的枝条剪掉. 如果估价函数写得