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

文章描述了一个关于木材切割的问题,农夫约翰需要特定规格的木材,但他只能从木材店获取大规格的木板并使用无损锯子进行切割。题目要求通过二分搜索配合剪枝策略,找出约翰能获得最多所需木板的数量。输入包括木材店库存和约翰需求,输出是最大满足量。文章提供了搜索和剪枝的解题思路及代码实现。
摘要由CSDN通过智能技术生成

题目描述

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

 

题目描述似乎缺失了关键信息,通常我会需要了解“P10780 食物”是什么具体的算法竞赛题目,它来自在线平台洛谷Luogu),以及该题目的大致背景、条件和目标。洛谷食物(Food)可能是某种数据结构算法问题,比如贪吃蛇、分配任务等。 然而,我可以给你提供一个通用的模板: **[洛谷 P10780 食物 - 题目解析]** 题目名称:P10780 食物(假设是关于食物分配或者饥饿游戏的问题) 链接:[插入实际题目链接] **背景:** 此题通常涉及动态规划或者搜索策略。场景可能是有n个参与者(选手或角色),每个都有特定的食物需求或者优先级,我们需要在有限的食物资源下合理分配。 **分析:** 1. **输入理解**:首先读入n个参与者的信息,包括每个人的需求量或优先级。 2. **状态定义**:可以定义dp[i][j]表示前i个人分配完成后剩余的食物能满足第j个人的最大程度。 3. **状态转移**:递推式可能涉及到选择当前人分配最多食物的版本,然后更新剩余的食物数。 4. **边界条件**:如果剩余食物不足以满足某人的需求,则考虑无法分配给他;如果没有食物,状态值设为0。 5. **优化策略**:可能需要对状态数组进行滚动更新,以减少空间复杂度。 **代码示例(伪代码或部分关键代码片段):** ```python # 假设函数分配_food(demand, remaining)计算分配给一个人后剩余的食物 def solve(foods): dp = [[0 for _ in range(max_demand + 1)] for _ in range(n)] dp = foods[:] # 从第一个到最后一个参与者处理 for i in range(1, n): for j in range(1, max_demand + 1): if dp[i-1][j] > 0: dp[i][j] = max(dp[i][j], dp[i-1][j] - foods[i]) dp[i][j] = max(dp[i][j], distribute_food_to(i, dp[i-1][j])) return dp[n-1][max_demand] ``` **相关问题--:** 1. 这道题是如何运用动态规划的? 2. 如果有优先级限制,应该如何调整代码? 3. 怎样设计搜索策略来解决类似问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值