信息学奥赛一本通 1442:【例题3】小木棍 | 洛谷 P1120 小木棍

【题目链接】

ybt 1442:【例题3】小木棍
洛谷 P1120 小木棍

注:【ybt 1442:【例题3】小木棍】不保证每个输入都 <=50,所以需要将>50的数据过滤掉,不考虑。

【题目考点】

1. 深搜剪枝

【解题思路】

所有的小木棍是由等长的原木棍切成的,该问题求的是原木棍的最小长度。
如果两根木棍长度相同,其作用也是相同的。因此长度相同的木棍可以作为一类元素。因此想到可以使用“桶”来保存各个木棍。设数组b,b[i]表示长度为i的木棍的数量。
先输入所有木棍,将相关信息保存在b数组中,同时记录最长木棍长度maxLen和最短木棍长度minLen,木棍总长度totLen。
所有小木棍是由若干原木棍切分而成的,那么木棍总长度,一定是原木棍长度的倍数。反过来,原木棍长度,一定是木棍总长度的约数。同时:原木棍的长度必须大于等于最长木棍的长度maxLen。
从小到大取木棍总长度totLen的约数l,看原木棍长度为l时,能否让所有小木棍拼成totLen/l个长为l的原木棍。如果成功拼成,则找到了可行的原木棍的最小长度。

深搜时需要记录的状态有:这一次要选择的木棍长度st,当前拼出的木棍长度len,剩余要拼的原木棍数量stickNum,要拼成的原木棍长度stickLen。
在拼一根原木棍的过程中

  1. 应该先选较长的小木棍,再选较短的小木棍,这样可以较快得到拼成木棍的方案。
  2. 每选择一根长为i的木棍,下一次要选择的木棍长度还为i,当前拼出木棍长度为len+1。如果长为i的木棍已经选过了,或不存在,则尝试选择下一个长度更短的木棍。
  3. 如果当前木棍长度len已经为原木棍长度stickLen,则拼好了一根木棍,接下来拼下一根木棍,从最长的木棍开始选择木棍,将st设为maxLen,当前拼出木棍长度len设为0,剩余要拼木棍数量stickNum减少1。
  4. 如果剩余要拼木棍数量stickNum变为了0,则使用小木棍成功拼出了若干个长为stickLen的原木棍,原木棍长度的最小值就是stickLen。

可以进行的剪枝有:

  1. 已经提到的:先选较长的小木棍,再选较短的小木棍(优化搜索顺序

  2. 如果当前木棍长度len已经比原木棍长度stickLen更长,就结束搜索。(可行性剪枝

  3. 如果已经得到可行的方案,即最小原木棍长度ans已经有值,后面即便得到可行方案,原木棍长度stickLen也会大于等于ans,因此就不用再搜索了。(最优性剪枝

  4. 假设当前拼好的长为len的木棍再拼接长为i的木棍就能拼成原木棍,即len+i == stickLen,将这种情况搜索完毕后,就不需要再进行搜索了。
    原因如下:接下来如果不用长为i的木棍,而是再向后搜索长度更短的木棍,那么接下来做的事情就是找长度加和为i的几根木棍

    • 如果后面不存在长度加和为i的几根木棍,则无法拼成这根原木棍,则当前的拼接过程应该结束,应该返回上一层,改变上一根选择的木棍。

    • 如果接下来还存在长度加和为i的几根木棍,对比两种情况。

      • 情况1:是已经搜索过的情况,将长为i的木棍和长为len的拼好的木棍进行拼接,得到一根原木棍。留下几根长度加和为i的木棍。
      • 情况2:是将几根长度加和为i的木棍和长为len的拼好的木棍进行拼接,得到一根原木棍。留下长为i的木棍。

      如果是情况1,留下几根长度加和为i的木棍,这些木棍可以合起来,当做一个长为i的木棍,此时这几根长度加和为i的木棍的作用和一根长度为i的木棍的作用相同。这几根木棍也可以拆开分别使用,会比留下长为i的木棍更加灵活。因此情况1接下来的搜索过程一定已经涵盖了情况2接下来的搜索过程,接下来没有再进行搜索的必要了。(排除等效冗余

  5. 如果当前刚开始要要拼一根原木棍,即已经拼成的木棍长度len==0。在确定使用的一根长为i的木棍后进行搜索,直至这一趟搜索结束。在这一趟搜索中:

    • 如果已经成功得到将小木棍拼成若干长为stickLen的原木棍的方案,那么已经找到解,自然不用再进行搜索了。
    • 如果没有得到可行的方案,接下来在拼当前原木棍的过程中,第一根木棍就该尝试使用长为i+1的木棍。而长为i的木棍还是存在的,它即便不参与拼接当前的原木棍,也一定会参与拼接下一根原木棍。
      而刚才的搜索过程中,对于长为i的木棍参与拼接原木棍的所有情况都已搜索完成,也没有找到一种可行的拼接方案。也就是说,在当前情况下,使用长为i的木棍的所有方案都是失败的,因此不用继续进行搜索了,应该回到上一层,改变上一根木棍的使用情况。(排除等效冗余

【题解代码】

解法1:深搜剪枝
#include <bits/stdc++.h>
using namespace std;
#define N 70
int b[N];//b[i]:长度为i的木棍个数(桶)   
int n, totLen, minLen = 70, maxLen, ans;
//st:遍历起始数字 len:当已前凑的木棍长度 stickNum:剩余需要凑的原木棍数量 stickLen:原木棍长度 
void dfs(int st, int len, int stickNum, int stickLen) 
{
	if(ans > 0)//如果已经求出结果,则不再搜索 
		return;
	if(stickNum == 0)//如果已经拼成指定数量的原木棍,则找到一种拼接方案 
	{
		ans = stickLen;//记录最短原木棍长度,结束整个搜索过程 
		return;
	}
	if(len == stickLen)//如果已经拼成一根原木棍 
	{
		dfs(maxLen, 0, stickNum-1, stickLen);//开始拼下一根原木棍,初始长度len为0,需要拼的木棍数量减1 
		return;
	}
	if(len > stickLen)//剪枝:如果当前木棍长度len已经比原木棍长度stickLen更长,就不再搜索 
		return;
	for(int i = st; i >= minLen; --i) if(b[i] > 0)//木棍长度i,从大到小遍历 
	{
		b[i]--;//使用了一根长为i的木棍 
		dfs(i, len+i, stickNum, stickLen);//刚才使用了长为i的木棍,下面还是从长为i的木棍看起 
		b[i]++;//状态还原 
		if(len == 0 || len+i == stickLen)//解题思路中剪枝第4、第5点。
			return;
	}
}
int main() 
{
	int a;
	cin >> n;
	for(int i = 1; i <= n; ++i)
	{
		cin >> a;
		if(a <= 50)//ybt 1442:【例题3】小木棍 不保证每个输入都 <=50,所以需要将>50的数据过滤掉 
		{
			b[a]++;//b[i]:长为i的木棍的数量
			totLen += a;//木棍总长
			maxLen = max(maxLen, a);//最长木棍长度
			minLen = min(minLen, a);//最短木棍长度
		}
	}
	for(int l = maxLen; l <= totLen; ++l) if(totLen%l == 0)//l:原木棍长度 必须是总长度的约数 
	{
		dfs(maxLen, 0, totLen/l, l);
		if(ans > 0)//找到解,就结束
			break; 
	}
	cout << ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值