木材加工问题

【问题描述】
木材厂有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目是给定了。当然,我们希望得到的小段越长越好,你的任务是计算能够得到的小段木头的最大长度。
木头长度的单位是厘米。原木的长度都是正整数,我们要求切割得到的小段木头的长度也要求是正整数。

【输入描述】
第一行是两个正整数N和K(1 ≤ N ≤ 10000, 1 ≤ K ≤ 10000),N是原木的数目,K是需要得到的小段的数目。
接下来的N行,每行有一个1到10000之间的正整数,表示一根原木的长度。

【输出描述】
输出能够切割得到的小段的最大长度。如果连1厘米长的小段都切不出来,输出”0″。

【样例输入1】
3 7
232
124
456

【样例输出1】

114

【样例输入2】
3 8
124
224
319

【样例输出2】

74

【分析】

以样例1为例

加入我以100为单位去切232、124、456这三块木头,能切成2+1+4=7,按照题目要求能切成7段,但是不是最优解,因为题目要求得到的段越长越好,而101也符合要求,而且比100切出的段更长,那么实际上这道题就是在求题目要求的最优解。

那么这道题目可以考虑使用二分法来求解。

有人问我可不可以用暴力穷举法,每种长度都试试,这样能得出结论但是在竞赛中大概率会超时。

我们假设left是切割小段的最短长度,left初始值为0,right是切割小段的最大长度,right的初始值为所有木材当中最长的那个。

可以看出,实际上我们

那么像这样的题用二分法有什么好处呢?


可以看出,上面的程序中,我们每次用mid值去且木头,
①第一次的值是1+1+1+1=4<7 ,没有达到目标值,说明mid值取的大了。应该取小点,所以修改右区间端点的值。
②第二次的是是2+1+1+2+1=7,mid取值切割后符合目标值,这里已经出现符号目标值的mid值,那为什么还要取第三次呢?这就是文章一开始讲的,mid值为100和101的时候都能切出7块,但显然101比100更优,因为题目中要求切出的块尽量大。
③第三次的值是2+1+1+1+1=6<7。说明mid值有点大,要继续划分区间。
④ …….

那么,上面这个过程需要我们至少要做两件事,第一就是结束条件,第二个就是切割木头符合时的判断。


num = 0;
      for (i = 0; i < n; i++)
      {
            if (num >= k) break;
            num = num + len[i] / mid ;
      }

这里的num可以表示最终切的段数,mid表示每次取的中值。


#include <iostream>
using namespace std;
int main()
{
      int n, k, len[10000], i, left, right, mid,num;
      cin>>n>>k; //读入木头数量和最小需要得到的小段数目 
      right = 0;

      for (i = 0; i < n; i++)
      {
            cin>>len[i];
            if (right < len[i]) right = len[i];  //用right取得最大长度。 
      }
      right++;  //从0开始的,此处要++
      left = 0 ;

      while ( left + 1 < right)   //结束条件,因为题目中都是整数。
      {
            mid = (left + right) / 2;
            num = 0;
            for (i = 0; i < n; i++)
            {
                  if (num >= k) break;
                  num = num + len[i] / mid ;
            }

            if ( num >= k )  //如果切的数量比k多 
                  left = mid;
            else
                  right = mid;
       }
      cout<<left<<endl;
      return 0;
}

除此之外,我们也可以写成函数形式。用一个函数来判断能否切割成合适的块数。


int f(int mid)
{
	if(mid==0) return false;
	int sum=0;

	for(int i=1;i<=n;i++)
	 sum+=a[i]/mid;

	if(sum>=m)  //切的块数大于等于给定的块数 
	 return true;

	return false;
}

然后,在主函数中调用即可。


#include <iostream>
using namespace std;
int n,m;
int a[100010],l,r=0,mid,maxn=0;  //maxn表示最终能切的的木段的最大长度

int f(int mid) {
	if(mid==0) return false;
	int sum=0;
	for(int i=1; i<=n; i++)
		sum+=a[i]/mid;
	if(sum>=m)
		return true;
	return false;
}

int main() {
	cin>>n>>m;
	for(int i=1; i<=n; i++) {
		cin>>a[i];
		r=max(r,a[i]);//用max 取得最大值 
	}

	l=1; //从1开始录入的 

	while(l<=r) {
		mid=(l+r)/2;
		if(f(mid)) {
			maxn=max(maxn,mid); 
			l=mid+1;
		} else
			r=mid-1;
	}
	cout<<maxn;
	return 0;
}


上面两个代码都借鉴了二分法的思想,不断缩小左右端点值,直到左右端点相遇为止。由于题目中给定的条件是 所有单位都是整数,那么左区间端点值和右区间端点值最多只差1。

后记

另外一些题目中,最后切的的单位不一定是整数,这个时候就需要在结束条件这个地方进行修改了。后面几篇博客中,会详细描述这种情况。


返回目录:算法