洛谷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;
}
运行结果

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

相关推荐
王老师青少年编程18 小时前
2024年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)
c++·题解·真题·csp·信奥赛·csp-s·提高组
凡人叶枫18 小时前
C++中输入、输出和文件操作详解(Linux实战版)| 从基础到项目落地,避坑指南
linux·服务器·c语言·开发语言·c++
CSDN_RTKLIB18 小时前
使用三方库头文件未使用导出符号情景
c++
zheyutao18 小时前
字符串哈希
算法
A尘埃19 小时前
保险公司车险理赔欺诈检测(随机森林)
算法·随机森林·机器学习
大江东去浪淘尽千古风流人物19 小时前
【VLN】VLN(Vision-and-Language Navigation视觉语言导航)算法本质,范式难点及解决方向(1)
人工智能·python·算法
rainbow688920 小时前
Linux文件描述符与重定向原理
c++
努力学算法的蒟蒻20 小时前
day79(2.7)——leetcode面试经典150
算法·leetcode·职场和发展
2401_8414956420 小时前
【LeetCode刷题】二叉树的层序遍历
数据结构·python·算法·leetcode·二叉树··队列
AC赳赳老秦20 小时前
2026国产算力新周期:DeepSeek实战适配英伟达H200,引领大模型训练效率跃升
大数据·前端·人工智能·算法·tidb·memcache·deepseek