洛谷P1120&UVA307 [CERC 1995] 小木棍

题目描述

乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50。

现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。

给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

输入

第一行是一个整数 n,表示小木棍的个数。

第二行有 n 个整数,表示各个木棍的长度 ai​。

对于全部测试点,1≤n≤65,1≤ai​≤50。

输出

输出一行一个整数表示答案。

输入输出样例

输入1:

9

5 2 1 5 2 1 5 2 1

输出1:

6

思路

观察可以发现,题目中n达到了65,如果使用普通的DFS深搜会TLE ,使用BFS广搜则会MLE

那么如果使用多个剪枝(包括但不限于优化搜索顺序,排除等效冗余,可行性和最优性)****, 是有可能通过的。

这里采用一种新的DFS叫做迭代加深搜索(IDS)。

IDS(迭代加深搜索) 是一种结合了深度优先搜索(DFS)空间效率与广度优先搜索(BFS)完备性的搜索策略。

它通过逐步增加深度限制进行多轮DFS。每一轮都在当前深度限制内进行搜索,若未找到解,则增加深度限制并重新开始搜索。

优势在于避免了DFS陷入过深分支的冗余,同时规避了BFS存储全部中间状态的内存开销。虽然存在重复搜索浅层节点的开销,但在状态空间未知或深度较大的问题中,它是一种非常有效的平衡选择。

所以,本题比较合理的做法是IDS。

一个近似的IDS框架:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,ans;
void dfs(int k,/*其他参数*/int maxdep)//当前看k层,最大搜索层数maxdep
{
    if(k>maxdep)//超出范围 
    {
        if(/*满足某样的条件*/)
        {
        	ans=/*......*/; 
		}
        return
    }
    if(sum>m)
    {
        return;
    }
    for(/**/)
    {
        dfs(k+1,maxdep);//下一层 
    }
}
int main()
{
    cin>>n;
    /*
    ...... 
	*/
    for(int d=1;d<=n;d++)//d:最大搜索层数 (这里假设到n)
    {
        dfs(1,d);
        if(ans>0)
        {
            break;
        }
    }
    cout<<ans;
    return 0;
}

来看一下过程:

STEP 1:读写优化,输入。为方便计算,这里进行预处理,计算最大最小长度,并统计这种木棍有多少。

STEP 2:枚举原始木棍长度(主函数内准备调用IDS)。

原棍长度 l 必须满足:

l >= maxlen(不能比最长段还短) sumlen % l == 0(整除才可能拼成整数根)从小到大枚举,第一个找到的就是最小可能长度。

STEP 3:开始写DFS.参数包括:

剩余段数n, 当前拼的长度len, 目标长度sticklen, 可尝试的最大段长度mx

STEP 3-1:DFS出口:若全部段用完且最后一段恰好拼完,说明找到解。否则若当前这根拼完(但没有全部拼完),则重置len=0,重新DFS

STEP 3-2:如何选择:在可试长度应 ≤ 剩余所需长度 sticklen-len,且不超过 mx(排除等效冗余)的基础上,从大到小尝试,加快找到可行组合。

STEP 3-3:回溯+剪枝:

如果当前是某根原棍的开头,放入段 i 失败,则相同长度段放在开头都会失败,直接 break 不再试更短段(因为排序尝试,后续更短段不可能更好)。

如果当前段放入后刚好拼满这根原棍,但后续搜索失败,则回溯。因为这是"最优填满"仍失败,无需再试其他段来填满这根棍子。

代码
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,a,sumlen,maxlen,minlen=100,b[70];//b[i]:长为i的木棍数量
bool hasans;
//n:还剩下木棍数量
//len:一拼成木棍长度
//sticklen:原木棍长度 
void dfs(int n,int len,int sticklen,int mx)
{
	/*剪枝1:全局最优剪枝-一旦找到答案,立即终止所有搜索*/
	if(hasans)
	{
		return;
	}
	/*终止条件:所有木棍已用完*/
	if(n==0)
	{
		if(len==sticklen)
		{
			hasans=true;
		}
		return;
	}
	/*剪枝2:当前木棍拼满剪枝-当前原棍拼满,立即开始下一根*/
	if(len==sticklen)
	{
		dfs(n,0,sticklen,maxlen);
		return;
	}
	/*剪枝3:搜索范围限制-只尝试不超过剩余长度和mx的长度*/
	/*剪枝4:搜索顺序优化-从大到小尝试,减少分支*/
	for(int i=min(mx,sticklen-len);i>=minlen;i--)
	{
		if(b[i]>0)
		{
			b[i]--;
			dfs(n-1,len+i,sticklen,i);
			b[i]++;//回溯恢复状态
			/*剪枝5:等效位置剪枝-开头失败不再试同长度开头*/
			/*剪枝6:最优填充剪枝-正好填满却失败则回溯*/
			if(len==0||len+i==sticklen)//排除等效冗余 
			{
				break;
			} 
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a;
		sumlen+=a;
		maxlen=max(maxlen,a);
		minlen=min(minlen,a);
		b[a]++;
	}
	/*剪枝7:合法性剪枝-原棍长度必须是总长度的约数*/
	/*剪枝8:枚举优化-长度从最长段开始枚举,确保最小可能长度*/
	for(int l=maxlen;l<=sumlen;l++)
	{
		if(sumlen%l==0)
		{
			dfs(n,0,l,maxlen);
			if(hasans)
			{
				cout<<l;
				return 0;
			}
		}
	}
    return 0;
}
运行结果

感谢阅读,我们下期再会。

相关推荐
清酒难咽20 小时前
算法案例之递归
c++·经验分享·算法
让我上个超影吧20 小时前
【力扣26&80】删除有序数组中的重复项
算法·leetcode
z203483152020 小时前
C++对象布局
开发语言·c++
张张努力变强21 小时前
C++ Date日期类的设计与实现全解析
java·开发语言·c++·算法
沉默-_-21 小时前
力扣hot100滑动窗口(C++)
数据结构·c++·学习·算法·滑动窗口
钱彬 (Qian Bin)21 小时前
项目实践19—全球证件智能识别系统(优化检索算法:从MobileNet转EfficientNet)
算法·全球证件识别
feifeigo1231 天前
基于EM算法的混合Copula MATLAB实现
开发语言·算法·matlab
漫随流水1 天前
leetcode回溯算法(78.子集)
数据结构·算法·leetcode·回溯算法
斐夷所非1 天前
C++ 继承、多态与类型转换 | 函数重载 / 隐藏 / 覆盖实现与基派生类指针转换
c++
IT猿手1 天前
六种智能优化算法(NOA、MA、PSO、GA、ZOA、SWO)求解23个基准测试函数(含参考文献及MATLAB代码)
开发语言·算法·matlab·无人机·无人机路径规划·最新多目标优化算法