[SCOI2005] 栅栏【题解】

简要题意

木材店老板给出一个整数 \(m\)\(m\) 个木板的长度。老板给出的木板可以随意无损耗切割。

约翰给出一个整数 \(n\) 和所需要的 \(n\) 个木板的长度。

求约翰能得到最多木板的个数。

分析

二分答案的题基本不会是单纯的二分,一般会带上一些优化、DP、搜索等……

这道题主要是运用搜索和剪枝,而且剪枝不止一个。

正常搜索肯定都会写,不过优化比较多。

优化一:贪心

既然木材店的木板都可以任意分割,那么将给出的 m 个木板排序,优先使用长木板。

而对于需要的木板来说,优先使用短木板才能使木板数量最大。

优化二:前缀和

用前缀和维护所需木板长度,记录给出木板总长度。

二分前判断所需木板总长度是否已经大于给出木板总长度,超过就向回退,直到木板足够。

优化三:剪枝一

记录浪费的木材数量。一块长木板被切割后,不使用的那段就可能被浪费。

搜索过程中,如果浪费掉的木材长度与所需木材的长度超过了给出木材总长度,则不继续向下搜索。

优化四:剪枝二

如果需要的两块相邻木板长度相等,我们就可以从上一次放的那一块枚举,可以节省大量计算。

Code

来自 Trump 的 Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>

using namespace std;

int n,m,g_wo[1145140],n_wo[1145140],pre[1145140];
int tot,mid,waste;

bool check( int num , int le ) {
	//num 还需要的木材数量 le 左边界
	if ( num == 0 ) return true;//全可以取到则返回true 
	if ( pre[mid] + waste > tot ) {
		//优化3 
		//若需要的木板长度和浪费长度加起来超过老板提供总长度 
		return false;
	}
	for ( int i = le; i <= n; ++i) {
		if ( g_wo[i] >= n_wo[num] ) {
			bool k;
			//g_wo[i]的长度大 可以锯下来n_wo[num]的长度
			g_wo[i] -= n_wo[num];
			//则先锯下来 方便向前遍历 一会儿回溯
			if ( g_wo[i] < n_wo[1] ) {
				//剩下的木材若连第一个(最短的)需要木板的长度都没有
				waste += g_wo[i];//则是无法使用的废料 
			}
			if ( n_wo[num] == n_wo[num - 1] ) {
				//优化4 
				k = check( num - 1 , i );
				//num - 1只用从i开始搜就行 
				//因为是降序排列,所以num - 1一定小于等于num表示的长度
				//若num前一个与num需要木材长度一样
				//则num - 1的长度一定大于i之前的提供木板的长度 
				//所以直接从i开始向后找 
			}
			else k = check( num - 1 , 1 );
			if ( g_wo[i] < n_wo[1] ) {
				waste -= g_wo[i];
				//回溯
				//前面因为是先减n_wo[1]再判断
				//所以这里的判断要先进行 
			}
			g_wo[i] += n_wo[num];//回溯
			if ( k == true ) return true;
			//入果已经有可能则直接返回ture 
		}
	}
	return false; 
}

int main() {
	scanf("%d",&m);
	for (int i = 1; i <= m; ++i) {
		scanf("%d",&g_wo[i]);//提供的木材 
		tot += g_wo[i];
	}
	scanf("%d",&n);
	for (int i = 1; i <= n; ++i) {
		scanf("%d",&n_wo[i]);//需要的木材 
	}
	sort( g_wo + 1 , g_wo + 1 + m );
	sort( n_wo + 1 , n_wo + 1 + n );
	//优化1 
	for (int i = 1; i <= n; ++i) {
		pre[i] = n_wo[i];
		pre[i] += pre[i - 1];//前缀和 
	}//优化2
	int l_an = 0,r_an = n;//表示最多的木板数
	while ( r_an > 0 && pre[r_an] > tot ) {
		r_an --;//若需要的长度大于提供长度 
	} 
	while ( l_an <= r_an ) {//正常二分 
		waste = 0;//记得初始化QWQ 
		mid = l_an + ( ( r_an - l_an ) >> 1 );
		if ( check( mid , 1 ) ) l_an = mid + 1;
		else r_an = mid - 1;
	} 
	//输出r是因为在l = r的情况下,若mid可取,则l会变成mid+1但r不变
	//若不可取则r会向前-1
	printf("%d\n",r_an);
	//二分答案一般二分的都是让输出的内容 
	return 0; 
}

总结

总的来说,这道题是以搜索为辅的二分答案,重点是优化很多。

经过一段时间思考想出了前两个优化,优化三四没想出来。

这道题提供了一种新的类型的节省计算的方法,还是很有价值的。

posted @ 2022-12-20 09:44  -白简-  阅读(102)  评论(0编辑  收藏  举报