每日一题 | 洛谷Luogu P2329[SCOI2005]栅栏 题解

题目描述

农夫约翰打算建立一个栅栏将他的牧场给围起来,因此他需要一些特定规格的木材。于是农夫约翰到木材店购买木材。可是木材店老板说他这里只剩下少部分大规格的木板了。不过约翰可以购买这些木板,然后切割成他所需要的规格。而且约翰有一把神奇的锯子,用它来锯木板,不会产生任何损失,也就是说长度为 1010 的木板可以切成长度为 88 和 22 的两个木板。

你的任务:给你约翰所需要的木板的规格,还有木材店老板能够给出的木材的规格,求约翰最多能够得到多少他所需要的木板。

输入格式

第一行为整数 �(�≤50)m(m≤50) 表示木材店老板可以提供多少块木材给约翰。紧跟着 �m 行为老板提供的每一块木板的长度。

接下来一行(即第 �+2m+2 行)为整数 �(�≤1000)n(n≤1000),表示约翰需要多少木材。

接下来 �n 行表示他所需要的每一块木板的长度。木材的规格小于 3276732767。(对于店老板提供的和约翰需要的每块木板,你只能使用一次)。

输出格式

只有一行,为约翰最多能够得到的符合条件的木板的个数。

输入输出样例

输入 #1

4
30
40
50
25
10
15
16
17
18
19
20
21
25
24
30

输出 #1

7

输入 #2

3
20
10
10
9
3
3
3
5
5
7
8
8
9

输出 #2

7
思路

这个题,洛谷给打的标签是搜索和剪枝

 那么,我们从搜索的角度考虑

 可以固定符合条件的木板数量,枚举木材店的木材,判断能否满足固定的木板数量

 整体思路:二分答案+剪枝dfs

二分答案

首先考虑二分答案的部分,我们要二分的是符合条件的木板数量

下界:0(木材店无法满足约翰的要求)

上界:n(约翰需要的木材数量)

l=0, r=n;
while (l<=r){
    mid = (l+r)>>1;    
    if (dfs(mid)){
        l = mid+1;
    }
    else r = mid-1;
}
printf("%d\n", r);//输出答案
dfs

我们贪心地考虑一下,如果我们满足约翰的mid个木材,那么我们要求这mid根木材尽可能小

所以,我们给约翰要求的木材长度升序排列,判断排序后约翰要求的前mid根木材能否被满足

代码中,我们用need数组表示约翰要求的木材长度,wood数组表示木材店可以提供的木材长度

用now来表示我们当前正要满足的约翰要求的木材(now的初值为mid),枚举wood数组中可以满足约翰要求的木材

如果可以满足约翰的需求(wood[i]>=need[now]),我们将wood[i]-need[now],紧接着将now--,考虑下一根约翰要求的木材

边界条件:

如果now==0,约翰的需求已经被满足了,我们return true

这样绝对是TLE的,我们考虑优化dfs

剪枝

1、可行性剪枝:

如果木材店剩余的木材都无法满足约翰的mid根木材的需求,直接return

如何判断?

对木材店的木材总量求和,存入变量sum

给dfs加一个参数left,保存多余的木材长度,当有单根长度小于每一根约翰要求的木材长度的木材店木材时,多余的木材长度要增加这根没用的木材的长度

那么,木材店剩余的木材 = 木材店木材总量-多余的木材长度

对排序后约翰要求的木材(need数组)做一个前缀和,存入数组s,如果s[mid]>木材店剩余木材长度,return false

    if (sum-left<s[mid]) return 0;
	int nxtLeft = left;
	if (wood[i]<need[1]) nxtLeft += wood[i];

在二分答案之前,我们同样可以用这种方法来缩短右端点

	int useless=0;//多余的木材长度,意义同left变量
	for (int i=1; i<=m; ++i){
		if (wood[i]<need[1]) useless += wood[i];
	}
	for (int i=1; i<=n; ++i) s[i] = s[i-1]+need[i];//前缀和
	l=0, r=n;
	while (s[r]>sum-useless && r) --r;//如果木材店剩余的木材都无法满足约翰的前r根木材的需求
	

2、去重(这个不加也可以AC,但是如果出现大量重复的数据,不加会很慢)

定义l为下一次枚举的木材店木材长度的左端点,并作为dfs的参数,出现重复的时候,将l定义为当前枚举的wood数组的元素的下标,下一次深搜时直接从l开始枚举,如果没有重复的时候,l=1


bool dfs(int now, int l, int left)
//用now来表示我们当前正要满足的约翰要求的木材
//l为下一次枚举wood数组的下表左端点
//left为多余的木材长度
{
	if (!now) return 1;//mid根木材可以被满足
	//可行性剪枝1
	//剩余木材长度小于约翰要求的mid根木材的长度,return
	if (sum-left<s[mid]) return 0;
	bool flag=0;
	for (int i=l; i<=m; ++i){
		if (wood[i]>=need[now]){//当前木材店的木材可以满足约翰第now根木材
			wood[i] -= need[now];//锯掉约翰要求的木材
			int nxtLeft = left;
			if (wood[i]<need[1]) nxtLeft += wood[i];
			//当有单根长度小于每一根约翰要求的木材长度的木材店木材时,多余的木材长度要增加这根没用的木材的长度
			if (need[now-1]==need[now]) flag = dfs(now-1, i, nxtLeft);//去重
			else flag = dfs(now-1, 1, nxtLeft);
			//回溯
			wood[i] += need[now];
			if (flag) return flag;
		}
	}
	return 0;
}

完整代码
#include<cstdio>
#include<algorithm>
using namespace std;

const int M = 55;
const int N = 1e3+5;
int m, n, l, r, mid, sum;//sum为木材店可提供的木材总长
int wood[M], need[N], s[N];
//wood存储木材店提供的木材长度,need存储约翰需要的木材长度,s是排序后need数组的前缀和

inline int read()//快读
{
	int x=0, f=1; char c=getchar();
	while (!(c>='0' && c<='9')){if (c=='-') f=-1; c=getchar();}
	while (c>='0' && c<='9'){x=(x<<3)+(x<<1)+c-48; c=getchar();}
	return f*x;
}

bool dfs(int now, int l, int left)
//用now来表示我们当前正要满足的约翰要求的木材
//l为下一次枚举wood数组的下表左端点
//left为多余的木材长度
{
	if (!now) return 1;//mid根木材可以被满足
	//可行性剪枝1
	//剩余木材长度小于约翰要求的mid根木材的长度,return
	if (sum-left<s[mid]) return 0;
	bool flag=0;
	for (int i=l; i<=m; ++i){
		if (wood[i]>=need[now]){//当前木材店的木材可以满足约翰第now根木材
			wood[i] -= need[now];//锯掉约翰要求的木材
			int nxtLeft = left;
			if (wood[i]<need[1]) nxtLeft += wood[i];
			//当有单根长度小于每一根约翰要求的木材长度的木材店木材时,多余的木材长度要增加这根没用的木材的长度
			if (need[now-1]==need[now]) flag = dfs(now-1, i, nxtLeft);//去重
			else flag = dfs(now-1, 1, nxtLeft);
			//回溯
			wood[i] += need[now];
			if (flag) return flag;
		}
	}
	return 0;
}

int main()
{
	m=read();
	for (int i=1; i<=m; ++i){
		wood[i] = read();
		sum += wood[i];
	}
	n=read();
	for (int i=1; i<=n; ++i) need[i] = read();
	sort(need+1, need+n+1);//need数组要升序排列
	
	int useless=0;//多余的木材长度,意义同left变量
	for (int i=1; i<=m; ++i){
		if (wood[i]<need[1]) useless += wood[i];
	}
	for (int i=1; i<=n; ++i) s[i] = s[i-1]+need[i];//前缀和
	l=0, r=n;
	while (s[r]>sum-useless && r) --r;//如果木材店剩余的木材都无法满足约翰的前r根木材的需求
	
	while (l<=r){//二分答案
		mid = (l+r)>>1;
		if (dfs(mid, 1, 0)){
			l = mid+1;
		}
		else r=mid-1;
	}
	printf("%d\n", r);
	return 0;
}
补充

上述代码评测结果

当不去重时,我们可以AC,但是有一个测试点明显慢了很多

当不用可行性优化时,我们会TLE 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值